@umituz/react-native-design-system 4.23.79 → 4.23.81
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 +10 -8
- package/src/atoms/AtomicInput.tsx +11 -48
- package/src/atoms/AtomicPicker.tsx +19 -94
- package/src/atoms/EmptyState.tsx +1 -1
- package/src/atoms/icon/AtomicIcon.tsx +0 -1
- package/src/atoms/icon/iconStore.ts +0 -2
- package/src/atoms/picker/components/PickerModal.tsx +40 -121
- package/src/device/infrastructure/services/PersistentDeviceIdService.ts +0 -4
- package/src/device/presentation/hooks/useDeviceInfo.ts +55 -149
- package/src/haptics/infrastructure/services/HapticService.ts +0 -1
- package/src/image/index.ts +2 -1
- package/src/image/presentation/hooks/useImageBatch.ts +2 -1
- package/src/index.ts +2 -2
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +1 -1
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +0 -5
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +4 -54
- package/src/init/createAppInitializer.ts +0 -1
- package/src/init/env/createEnvConfig.ts +0 -1
- package/src/init/useAppInitialization.ts +0 -1
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +1 -1
- package/src/media/infrastructure/services/CardMediaOptimizerService.ts +0 -1
- package/src/media/infrastructure/services/CardMediaUploadService.ts +0 -1
- package/src/media/presentation/hooks/useCardMediaGeneration.ts +1 -1
- package/src/media/presentation/hooks/useCardMediaUpload.ts +1 -1
- package/src/media/presentation/hooks/useCardMediaValidation.ts +1 -1
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +1 -1
- package/src/media/presentation/hooks/useMedia.ts +0 -1
- package/src/media/presentation/hooks/useMediaGeneration.ts +1 -1
- package/src/media/presentation/hooks/useMediaUpload.ts +1 -1
- package/src/media/presentation/hooks/useMediaValidation.ts +1 -1
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +1 -1
- package/src/molecules/BaseModal.tsx +1 -3
- package/src/molecules/ConfirmationModalContent.tsx +1 -1
- package/src/molecules/ConfirmationModalMain.tsx +1 -1
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +0 -3
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +100 -179
- package/src/molecules/calendar/infrastructure/stores/useCalendarEvents.ts +0 -1
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +1 -1
- package/src/molecules/countdown/components/Countdown.tsx +1 -1
- package/src/molecules/navigation/StackNavigator.tsx +0 -1
- package/src/molecules/navigation/TabsNavigator.tsx +0 -1
- package/src/molecules/navigation/utils/AppNavigation.ts +0 -8
- package/src/molecules/splash/components/SplashScreen.tsx +0 -4
- package/src/offline/infrastructure/events/NetworkEvents.ts +0 -1
- package/src/offline/infrastructure/utils/healthCheck.ts +0 -5
- package/src/offline/presentation/hooks/useOffline.ts +0 -1
- package/src/offline/presentation/hooks/useOfflineWithMutations.ts +0 -2
- package/src/onboarding/index.ts +0 -1
- package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +0 -1
- package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +0 -2
- package/src/onboarding/presentation/hooks/useOnboardingScreenHandlers.ts +0 -2
- package/src/onboarding/presentation/hooks/useOnboardingScreenState.ts +0 -1
- package/src/onboarding/presentation/screens/OnboardingScreen.tsx +0 -3
- package/src/organisms/FormContainer.tsx +1 -1
- package/src/services/api/ApiClient.ts +42 -135
- package/src/storage/cache/domain/Cache.ts +0 -2
- package/src/storage/cache/infrastructure/TTLCache.ts +0 -3
- package/src/storage/domain/utils/devUtils.ts +0 -3
- package/src/storage/infrastructure/adapters/StorageService.ts +0 -3
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +0 -1
- package/src/tanstack/domain/config/QueryClientAccessor.ts +0 -1
- package/src/tanstack/domain/repositories/BaseRepository.ts +4 -4
- package/src/tanstack/domain/repositories/IBaseRepository.ts +3 -5
- package/src/tanstack/domain/repositories/RepositoryFactory.ts +0 -2
- package/src/tanstack/domain/repositories/mixins/repositoryInvalidationMethods.ts +10 -11
- package/src/tanstack/domain/repositories/mixins/repositoryQueryMethods.ts +11 -11
- package/src/tanstack/domain/utils/ErrorHelpers.ts +0 -1
- package/src/tanstack/infrastructure/config/PersisterConfig.ts +0 -7
- package/src/tanstack/infrastructure/config/QueryClientConfig.ts +0 -1
- package/src/tanstack/infrastructure/monitoring/DevMonitorLogger.ts +0 -6
- package/src/tanstack/presentation/hooks/useInvalidateQueries.ts +0 -4
- package/src/tanstack/presentation/hooks/useOptimisticUpdate.ts +0 -2
- package/src/tanstack/presentation/hooks/usePrefetch.ts +18 -119
- package/src/theme/core/CustomColors.ts +4 -122
- package/src/theme/infrastructure/storage/ThemeStorage.ts +0 -1
- package/src/typography/presentation/utils/textColorUtils.ts +36 -163
- package/src/exception/domain/entities/ExceptionEntity.ts +0 -115
- package/src/exception/domain/repositories/IExceptionRepository.ts +0 -37
- package/src/exception/index.ts +0 -66
- package/src/exception/infrastructure/services/ExceptionHandler.ts +0 -93
- package/src/exception/infrastructure/services/ExceptionLogger.ts +0 -142
- package/src/exception/infrastructure/services/ExceptionReporter.ts +0 -134
- package/src/exception/infrastructure/services/ExceptionService.ts +0 -166
- package/src/exception/infrastructure/storage/ExceptionStore.ts +0 -44
- package/src/exception/presentation/components/ErrorBoundary.tsx +0 -129
- package/src/exception/presentation/components/ExceptionEmptyState.tsx +0 -123
- package/src/exception/presentation/components/ExceptionErrorState.tsx +0 -122
- package/src/tanstack/presentation/hooks/utils/prefetchLogger.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.81",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -38,6 +38,12 @@
|
|
|
38
38
|
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
39
39
|
"version:major": "npm version major -m 'chore: release v%s'"
|
|
40
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
43
|
+
"@react-native-community/datetimepicker": "^8.6.0",
|
|
44
|
+
"react-native-svg": "^15.12.1",
|
|
45
|
+
"rn-emoji-keyboard": "^1.7.0"
|
|
46
|
+
},
|
|
41
47
|
"keywords": [
|
|
42
48
|
"react-native",
|
|
43
49
|
"design-system",
|
|
@@ -64,8 +70,6 @@
|
|
|
64
70
|
},
|
|
65
71
|
"peerDependencies": {
|
|
66
72
|
"expo": ">=54.0.0",
|
|
67
|
-
"@react-native-async-storage/async-storage": ">=1.18.0",
|
|
68
|
-
"@react-native-community/datetimepicker": ">=8.0.0",
|
|
69
73
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
70
74
|
"@react-navigation/native": ">=7.0.0",
|
|
71
75
|
"@react-navigation/stack": ">=7.0.0",
|
|
@@ -86,11 +90,9 @@
|
|
|
86
90
|
"expo-sharing": ">=12.0.0",
|
|
87
91
|
"expo-video": ">=3.0.0",
|
|
88
92
|
"react": ">=19.0.0",
|
|
89
|
-
"react-native": "0.81.
|
|
93
|
+
"react-native": "0.81.4",
|
|
90
94
|
"react-native-gesture-handler": ">=2.20.0",
|
|
91
|
-
"react-native-safe-area-context": ">=5.
|
|
92
|
-
"react-native-svg": ">=15.0.0",
|
|
93
|
-
"rn-emoji-keyboard": ">=1.7.0",
|
|
95
|
+
"react-native-safe-area-context": ">=5.6.2",
|
|
94
96
|
"zustand": ">=5.0.0"
|
|
95
97
|
},
|
|
96
98
|
"peerDependenciesMeta": {
|
|
@@ -141,7 +143,7 @@
|
|
|
141
143
|
"expo-sharing": "~14.0.8",
|
|
142
144
|
"expo-video": "~3.0.15",
|
|
143
145
|
"react": "19.1.0",
|
|
144
|
-
"react-native": "0.81.
|
|
146
|
+
"react-native": "0.81.4",
|
|
145
147
|
"react-native-gesture-handler": "^2.20.0",
|
|
146
148
|
"react-native-safe-area-context": "^5.6.0",
|
|
147
149
|
"react-native-svg": "15.12.1",
|
|
@@ -9,18 +9,6 @@ import { InputLabel } from './input/components/InputLabel';
|
|
|
9
9
|
import { InputIcon } from './input/components/InputIcon';
|
|
10
10
|
import { InputHelper } from './input/components/InputHelper';
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* AtomicInput - Pure React Native Text Input
|
|
14
|
-
*
|
|
15
|
-
* Features:
|
|
16
|
-
* - Pure React Native implementation (no Paper dependency)
|
|
17
|
-
* - Icon support via AtomicIcon (app provides renderer)
|
|
18
|
-
* - Outlined/filled/flat variants
|
|
19
|
-
* - Error, success, disabled states
|
|
20
|
-
* - Character counter
|
|
21
|
-
* - Responsive sizing
|
|
22
|
-
* - Full accessibility support
|
|
23
|
-
*/
|
|
24
12
|
export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>, AtomicInputProps>(({
|
|
25
13
|
variant = 'outlined',
|
|
26
14
|
state = 'default',
|
|
@@ -79,22 +67,10 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
79
67
|
const hasSuccess = state === 'success';
|
|
80
68
|
|
|
81
69
|
const sizeConfig = getSizeConfig({ size, tokens });
|
|
82
|
-
const variantStyle = getVariantStyle({
|
|
83
|
-
|
|
84
|
-
isFocused,
|
|
85
|
-
hasError,
|
|
86
|
-
hasSuccess,
|
|
87
|
-
isDisabled,
|
|
88
|
-
tokens,
|
|
89
|
-
});
|
|
90
|
-
const textColor = getTextColor({
|
|
91
|
-
isDisabled,
|
|
92
|
-
hasError,
|
|
93
|
-
hasSuccess,
|
|
94
|
-
tokens,
|
|
95
|
-
});
|
|
96
|
-
|
|
70
|
+
const variantStyle = getVariantStyle({ variant, isFocused, hasError, hasSuccess, isDisabled, tokens });
|
|
71
|
+
const textColor = getTextColor({ isDisabled, hasError, hasSuccess, tokens });
|
|
97
72
|
const iconColor = isDisabled ? tokens.colors.textDisabled : tokens.colors.textSecondary;
|
|
73
|
+
const iconPadding = sizeConfig.iconSize + 8;
|
|
98
74
|
|
|
99
75
|
const containerStyle: StyleProp<ViewStyle> = [
|
|
100
76
|
inputStyles.container,
|
|
@@ -117,9 +93,9 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
117
93
|
lineHeight: (sizeConfig.fontSize || 16) * 1.2,
|
|
118
94
|
color: textColor,
|
|
119
95
|
paddingVertical: 4,
|
|
96
|
+
paddingLeft: leadingIcon ? iconPadding : undefined,
|
|
97
|
+
paddingRight: (trailingIcon || showPasswordToggle) ? iconPadding : undefined,
|
|
120
98
|
},
|
|
121
|
-
leadingIcon ? { paddingLeft: sizeConfig.iconSize + 8 } : undefined,
|
|
122
|
-
(trailingIcon || showPasswordToggle) ? { paddingRight: sizeConfig.iconSize + 8 } : undefined,
|
|
123
99
|
inputStyle,
|
|
124
100
|
];
|
|
125
101
|
|
|
@@ -128,14 +104,7 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
128
104
|
<InputLabel label={label} state={state} />
|
|
129
105
|
|
|
130
106
|
<View style={containerStyle}>
|
|
131
|
-
{leadingIcon &&
|
|
132
|
-
<InputIcon
|
|
133
|
-
name={leadingIcon }
|
|
134
|
-
size={sizeConfig.iconSize}
|
|
135
|
-
color={iconColor}
|
|
136
|
-
position="leading"
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
107
|
+
{leadingIcon && <InputIcon name={leadingIcon} size={sizeConfig.iconSize} color={iconColor} position="leading" />}
|
|
139
108
|
|
|
140
109
|
<TextInput
|
|
141
110
|
ref={ref}
|
|
@@ -158,31 +127,25 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
158
127
|
numberOfLines={numberOfLines}
|
|
159
128
|
textContentType={textContentType}
|
|
160
129
|
style={textInputStyle}
|
|
161
|
-
onBlur={() => {
|
|
162
|
-
|
|
163
|
-
onBlur?.();
|
|
164
|
-
}}
|
|
165
|
-
onFocus={() => {
|
|
166
|
-
setIsFocused(true);
|
|
167
|
-
onFocus?.();
|
|
168
|
-
}}
|
|
130
|
+
onBlur={() => { setIsFocused(false); onBlur?.(); }}
|
|
131
|
+
onFocus={() => { setIsFocused(true); onFocus?.(); }}
|
|
169
132
|
testID={testID ? `${testID}-input` : undefined}
|
|
170
133
|
/>
|
|
171
134
|
|
|
172
|
-
{
|
|
135
|
+
{showPasswordToggle && secureTextEntry && (
|
|
173
136
|
<InputIcon
|
|
174
137
|
name={isPasswordVisible ? "eye-off-outline" : "eye-outline"}
|
|
175
138
|
size={sizeConfig.iconSize}
|
|
176
139
|
color={iconColor}
|
|
177
140
|
position="trailing"
|
|
178
|
-
onPress={
|
|
141
|
+
onPress={togglePasswordVisibility}
|
|
179
142
|
testID={testID ? `${testID}-toggle-password` : undefined}
|
|
180
143
|
/>
|
|
181
144
|
)}
|
|
182
145
|
|
|
183
146
|
{trailingIcon && !showPasswordToggle && (
|
|
184
147
|
<InputIcon
|
|
185
|
-
name={trailingIcon
|
|
148
|
+
name={trailingIcon}
|
|
186
149
|
size={sizeConfig.iconSize}
|
|
187
150
|
color={iconColor}
|
|
188
151
|
position="trailing"
|
|
@@ -2,53 +2,12 @@
|
|
|
2
2
|
* AtomicPicker Component
|
|
3
3
|
*
|
|
4
4
|
* A reusable option picker/dropdown component for selecting from a list of options.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Single and multi-select support
|
|
8
|
-
* - Modal display mode (full-screen on mobile)
|
|
9
|
-
* - Optional search/filter capability
|
|
10
|
-
* - Error and disabled states
|
|
11
|
-
* - Theme-aware styling
|
|
12
|
-
* - Icons for options
|
|
13
|
-
* - Clearable selection
|
|
14
|
-
* - react-hook-form integration ready
|
|
15
|
-
*
|
|
16
|
-
* Architecture:
|
|
17
|
-
* - Follows AtomicButton pattern with separated types and styles
|
|
18
|
-
* - Uses helper functions from picker/styles/pickerStyles.ts
|
|
19
|
-
* - Types defined in picker/types/index.ts
|
|
20
|
-
* - Zero inline StyleSheet.create()
|
|
21
|
-
*
|
|
22
|
-
* Usage:
|
|
23
|
-
* ```tsx
|
|
24
|
-
* const [partyType, setPartyType] = useState('birthday');
|
|
25
|
-
*
|
|
26
|
-
* <AtomicPicker
|
|
27
|
-
* value={partyType}
|
|
28
|
-
* onChange={setPartyType}
|
|
29
|
-
* options={[
|
|
30
|
-
* { label: 'Birthday Party', value: 'birthday', icon: 'cake' },
|
|
31
|
-
* { label: 'Wedding', value: 'wedding', icon: 'heart' },
|
|
32
|
-
* { label: 'Corporate Event', value: 'corporate', icon: 'briefcase' },
|
|
33
|
-
* ]}
|
|
34
|
-
* label="Party Type"
|
|
35
|
-
* placeholder="Select party type"
|
|
36
|
-
* searchable
|
|
37
|
-
* />
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* @module AtomicPicker
|
|
41
5
|
*/
|
|
42
6
|
|
|
43
7
|
import React from 'react';
|
|
44
|
-
import {
|
|
45
|
-
View,
|
|
46
|
-
TouchableOpacity,
|
|
47
|
-
StyleSheet,
|
|
48
|
-
} from 'react-native';
|
|
8
|
+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
49
9
|
import { useAppDesignTokens } from '../theme';
|
|
50
10
|
import { AtomicPickerProps } from './picker/types';
|
|
51
|
-
|
|
52
11
|
import { AtomicText } from './AtomicText';
|
|
53
12
|
import { PickerModal } from './picker/components/PickerModal';
|
|
54
13
|
import { PickerChips } from './picker/components/PickerChips';
|
|
@@ -64,12 +23,6 @@ import { usePickerState } from './picker/hooks/usePickerState';
|
|
|
64
23
|
|
|
65
24
|
export type { AtomicPickerProps, PickerOption, PickerSize } from './picker/types';
|
|
66
25
|
|
|
67
|
-
/**
|
|
68
|
-
* AtomicPicker - Universal option picker component
|
|
69
|
-
*
|
|
70
|
-
* Displays a button that opens a modal for selection.
|
|
71
|
-
* Supports single/multi-select, search, and custom rendering.
|
|
72
|
-
*/
|
|
73
26
|
export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
74
27
|
value,
|
|
75
28
|
onChange,
|
|
@@ -93,70 +46,45 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
|
93
46
|
testID,
|
|
94
47
|
}) => {
|
|
95
48
|
const tokens = useAppDesignTokens();
|
|
49
|
+
const pickerState = usePickerState({ value, multiple, options, placeholder, autoClose, onChange });
|
|
96
50
|
|
|
97
|
-
const pickerState = usePickerState({
|
|
98
|
-
value,
|
|
99
|
-
multiple,
|
|
100
|
-
options,
|
|
101
|
-
placeholder,
|
|
102
|
-
autoClose,
|
|
103
|
-
onChange,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Get style helpers with design tokens
|
|
107
51
|
const containerStyles = getPickerContainerStyles(tokens);
|
|
108
52
|
const labelStyles = getPickerLabelStyles(tokens);
|
|
109
53
|
const placeholderStyles = getPickerPlaceholderStyles(tokens);
|
|
110
54
|
const valueStyles = getPickerValueStyles(tokens);
|
|
111
55
|
const errorStyles = getPickerErrorStyles(tokens);
|
|
112
56
|
|
|
113
|
-
const
|
|
114
|
-
containerStyles.base,
|
|
115
|
-
containerStyles.size[size],
|
|
116
|
-
error ? containerStyles.state.error : undefined,
|
|
117
|
-
disabled ? containerStyles.state.disabled : undefined,
|
|
118
|
-
style,
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
const pickerLabelStyle = StyleSheet.flatten([
|
|
122
|
-
labelStyles.base,
|
|
123
|
-
labelStyles.size[size],
|
|
124
|
-
labelStyle,
|
|
125
|
-
]);
|
|
126
|
-
|
|
127
|
-
const pickerValueStyle = StyleSheet.flatten([
|
|
128
|
-
pickerState.selectedOptions.length > 0 ? valueStyles.base : placeholderStyles.base,
|
|
129
|
-
pickerState.selectedOptions.length > 0
|
|
130
|
-
? valueStyles.size[size]
|
|
131
|
-
: placeholderStyles.size[size],
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
const handleOpenModal = () => {
|
|
135
|
-
if (disabled) return;
|
|
136
|
-
pickerState.openModal();
|
|
137
|
-
};
|
|
57
|
+
const hasSelection = pickerState.selectedOptions.length > 0;
|
|
138
58
|
|
|
139
59
|
return (
|
|
140
60
|
<View>
|
|
141
|
-
{
|
|
142
|
-
{label && <AtomicText style={pickerLabelStyle}>{label}</AtomicText>}
|
|
61
|
+
{label && <AtomicText style={StyleSheet.flatten([labelStyles.base, labelStyles.size[size], labelStyle])}>{label}</AtomicText>}
|
|
143
62
|
|
|
144
|
-
{/* Picker Button */}
|
|
145
63
|
<TouchableOpacity
|
|
146
|
-
onPress={
|
|
64
|
+
onPress={() => !disabled && pickerState.openModal()}
|
|
147
65
|
disabled={disabled}
|
|
148
66
|
accessibilityRole="button"
|
|
149
67
|
accessibilityLabel={label || placeholder}
|
|
150
68
|
accessibilityState={{ disabled }}
|
|
151
69
|
testID={testID}
|
|
152
|
-
style={
|
|
70
|
+
style={StyleSheet.flatten([
|
|
71
|
+
containerStyles.base,
|
|
72
|
+
containerStyles.size[size],
|
|
73
|
+
error && containerStyles.state.error,
|
|
74
|
+
disabled && containerStyles.state.disabled,
|
|
75
|
+
style,
|
|
76
|
+
])}
|
|
153
77
|
>
|
|
154
|
-
|
|
155
|
-
|
|
78
|
+
<AtomicText
|
|
79
|
+
style={StyleSheet.flatten([
|
|
80
|
+
hasSelection ? valueStyles.base : placeholderStyles.base,
|
|
81
|
+
hasSelection ? valueStyles.size[size] : placeholderStyles.size[size],
|
|
82
|
+
])}
|
|
83
|
+
numberOfLines={1}
|
|
84
|
+
>
|
|
156
85
|
{pickerState.displayText}
|
|
157
86
|
</AtomicText>
|
|
158
87
|
|
|
159
|
-
{/* Icons */}
|
|
160
88
|
<PickerIcons
|
|
161
89
|
clearable={clearable}
|
|
162
90
|
disabled={disabled}
|
|
@@ -168,17 +96,14 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
|
168
96
|
/>
|
|
169
97
|
</TouchableOpacity>
|
|
170
98
|
|
|
171
|
-
{/* Selected Chips (Multi-select) */}
|
|
172
99
|
<PickerChips
|
|
173
100
|
selectedOptions={pickerState.selectedOptions}
|
|
174
101
|
onRemoveChip={pickerState.handleChipRemove}
|
|
175
102
|
testID={testID}
|
|
176
103
|
/>
|
|
177
104
|
|
|
178
|
-
{/* Error Message */}
|
|
179
105
|
{error && <AtomicText style={errorStyles}>{error}</AtomicText>}
|
|
180
106
|
|
|
181
|
-
{/* Selection Modal */}
|
|
182
107
|
<PickerModal
|
|
183
108
|
visible={pickerState.modalVisible}
|
|
184
109
|
onClose={pickerState.closeModal}
|
package/src/atoms/EmptyState.tsx
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Purpose: Empty state indication across all apps
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React, {
|
|
10
|
+
import React, { useMemo } from 'react';
|
|
11
11
|
import { View, StyleSheet, TouchableOpacity, ViewStyle } from 'react-native';
|
|
12
12
|
import { AtomicIcon } from './icon';
|
|
13
13
|
import { AtomicText } from './AtomicText';
|
|
@@ -122,7 +122,6 @@ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(
|
|
|
122
122
|
// No icon renderer provided - warn in dev and render nothing
|
|
123
123
|
if (!iconRenderer) {
|
|
124
124
|
if (__DEV__) {
|
|
125
|
-
console.warn(
|
|
126
125
|
'[DesignSystem] AtomicIcon requires an iconRenderer in DesignSystemProvider.\n' +
|
|
127
126
|
'Example:\n' +
|
|
128
127
|
'<DesignSystemProvider\n' +
|
|
@@ -75,7 +75,6 @@ export const useIconStore = create<IconStore>((set) => ({
|
|
|
75
75
|
if (__DEV__) {
|
|
76
76
|
const missingKeys = REQUIRED_ICON_KEYS.filter(key => !iconNames[key]);
|
|
77
77
|
if (missingKeys.length > 0) {
|
|
78
|
-
console.error(
|
|
79
78
|
`[DesignSystem] Missing icon names: ${missingKeys.join(', ')}`
|
|
80
79
|
);
|
|
81
80
|
}
|
|
@@ -101,7 +100,6 @@ export const useIconName = (key: keyof IconNames): string => {
|
|
|
101
100
|
|
|
102
101
|
if (!iconNames) {
|
|
103
102
|
if (__DEV__) {
|
|
104
|
-
console.warn(
|
|
105
103
|
`[DesignSystem] useIconName("${key}") - iconNames not configured.`
|
|
106
104
|
);
|
|
107
105
|
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PickerModal - Selection modal for AtomicPicker
|
|
3
|
-
* Handles search, filtering, and option selection.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import React from 'react';
|
|
7
|
-
import {
|
|
8
|
-
View,
|
|
9
|
-
Modal,
|
|
10
|
-
FlatList,
|
|
11
|
-
TextInput,
|
|
12
|
-
TouchableOpacity,
|
|
13
|
-
} from 'react-native';
|
|
6
|
+
import { View, Modal, FlatList, TextInput, TouchableOpacity } from 'react-native';
|
|
14
7
|
import { useSafeAreaInsets } from '../../../safe-area';
|
|
15
8
|
import { useAppDesignTokens } from '../../../theme';
|
|
16
9
|
import { PickerOption } from '../types';
|
|
@@ -42,9 +35,9 @@ interface PickerModalProps {
|
|
|
42
35
|
onSearchChange: (query: string) => void;
|
|
43
36
|
filteredOptions: PickerOption[];
|
|
44
37
|
multiple?: boolean;
|
|
45
|
-
emptyMessage?: string;
|
|
46
|
-
searchPlaceholder?: string;
|
|
47
|
-
closeAccessibilityLabel?: string;
|
|
38
|
+
emptyMessage?: string;
|
|
39
|
+
searchPlaceholder?: string;
|
|
40
|
+
closeAccessibilityLabel?: string;
|
|
48
41
|
testID?: string;
|
|
49
42
|
}
|
|
50
43
|
|
|
@@ -65,145 +58,71 @@ export const PickerModal: React.FC<PickerModalProps> = React.memo(({
|
|
|
65
58
|
}) => {
|
|
66
59
|
const tokens = useAppDesignTokens();
|
|
67
60
|
const insets = useSafeAreaInsets();
|
|
68
|
-
const
|
|
69
|
-
const searchIcon = useIconName('search');
|
|
70
|
-
const closeIcon = useIconName('close');
|
|
71
|
-
const infoIcon = useIconName('info');
|
|
72
|
-
|
|
73
|
-
const modalOverlayStyles = getModalOverlayStyles();
|
|
74
|
-
const modalContainerStyles = getModalContainerStyles(tokens, 0);
|
|
75
|
-
const modalHeaderStyles = getModalHeaderStyles(tokens);
|
|
76
|
-
const modalTitleStyles = getModalTitleStyles(tokens);
|
|
77
|
-
const searchContainerStyles = getSearchContainerStyles(tokens);
|
|
78
|
-
const searchInputStyles = getSearchInputStyles(tokens);
|
|
79
|
-
const emptyStateStyles = getEmptyStateStyles(tokens);
|
|
80
|
-
const emptyStateTextStyles = getEmptyStateTextStyles(tokens);
|
|
81
|
-
|
|
82
|
-
const safeSelectedValues = selectedValues ?? [];
|
|
61
|
+
const icons = { checkCircle: useIconName('checkCircle'), search: useIconName('search'), close: useIconName('close'), info: useIconName('info') };
|
|
83
62
|
|
|
84
|
-
const
|
|
85
|
-
|
|
63
|
+
const styles = {
|
|
64
|
+
overlay: getModalOverlayStyles(),
|
|
65
|
+
container: getModalContainerStyles(tokens, 0),
|
|
66
|
+
header: getModalHeaderStyles(tokens),
|
|
67
|
+
title: getModalTitleStyles(tokens),
|
|
68
|
+
search: getSearchContainerStyles(tokens),
|
|
69
|
+
searchInput: getSearchInputStyles(tokens),
|
|
70
|
+
empty: getEmptyStateStyles(tokens),
|
|
71
|
+
emptyText: getEmptyStateTextStyles(tokens),
|
|
86
72
|
};
|
|
87
73
|
|
|
74
|
+
const isSelected = (value: string) => selectedValues?.includes(value) ?? false;
|
|
75
|
+
|
|
88
76
|
const renderOption = ({ item }: { item: PickerOption }) => {
|
|
89
77
|
const selected = isSelected(item.value);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
const optionContainerStyle = getOptionContainerStyles(
|
|
93
|
-
tokens,
|
|
94
|
-
selected,
|
|
95
|
-
itemDisabled
|
|
96
|
-
);
|
|
97
|
-
const optionTextStyle = getOptionTextStyles(tokens, selected);
|
|
98
|
-
const optionDescriptionStyle = getOptionDescriptionStyles(tokens);
|
|
78
|
+
const disabled = item.disabled || false;
|
|
99
79
|
|
|
100
80
|
return (
|
|
101
81
|
<TouchableOpacity
|
|
102
|
-
onPress={() => !
|
|
103
|
-
disabled={
|
|
82
|
+
onPress={() => !disabled && onSelect(item.value)}
|
|
83
|
+
disabled={disabled}
|
|
104
84
|
testID={item.testID || `${testID}-option-${item.value}`}
|
|
105
|
-
style={
|
|
85
|
+
style={getOptionContainerStyles(tokens, selected, disabled)}
|
|
106
86
|
>
|
|
107
|
-
{
|
|
108
|
-
{item.icon && (
|
|
109
|
-
<AtomicIcon
|
|
110
|
-
name={item.icon}
|
|
111
|
-
size="md"
|
|
112
|
-
color={selected ? 'primary' : 'secondary'}
|
|
113
|
-
/>
|
|
114
|
-
)}
|
|
115
|
-
|
|
116
|
-
{/* Option Content */}
|
|
87
|
+
{item.icon && <AtomicIcon name={item.icon} size="md" color={selected ? 'primary' : 'secondary'} />}
|
|
117
88
|
<View style={{ flex: 1 }}>
|
|
118
|
-
<AtomicText style={
|
|
119
|
-
{item.description && (
|
|
120
|
-
<AtomicText style={optionDescriptionStyle}>
|
|
121
|
-
{item.description}
|
|
122
|
-
</AtomicText>
|
|
123
|
-
)}
|
|
89
|
+
<AtomicText style={getOptionTextStyles(tokens, selected)}>{item.label}</AtomicText>
|
|
90
|
+
{item.description && <AtomicText style={getOptionDescriptionStyles(tokens)}>{item.description}</AtomicText>}
|
|
124
91
|
</View>
|
|
125
|
-
|
|
126
|
-
{/* Selected Indicator */}
|
|
127
|
-
{selected && (
|
|
128
|
-
<AtomicIcon name={checkCircleIcon} size="md" color="primary" />
|
|
129
|
-
)}
|
|
92
|
+
{selected && <AtomicIcon name={icons.checkCircle} size="md" color="primary" />}
|
|
130
93
|
</TouchableOpacity>
|
|
131
94
|
);
|
|
132
95
|
};
|
|
133
96
|
|
|
134
97
|
return (
|
|
135
|
-
<Modal
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<View style={modalOverlayStyles}>
|
|
143
|
-
<View
|
|
144
|
-
style={[
|
|
145
|
-
modalContainerStyles,
|
|
146
|
-
{ paddingBottom: insets.bottom + tokens.spacing.md },
|
|
147
|
-
]}
|
|
148
|
-
>
|
|
149
|
-
{/* Modal Header */}
|
|
150
|
-
<View style={modalHeaderStyles}>
|
|
151
|
-
{/* Title */}
|
|
152
|
-
<AtomicText style={modalTitleStyles}>
|
|
153
|
-
{title || 'Select'}
|
|
154
|
-
</AtomicText>
|
|
155
|
-
|
|
156
|
-
{/* Close Button */}
|
|
157
|
-
<TouchableOpacity
|
|
158
|
-
onPress={onClose}
|
|
159
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
160
|
-
accessibilityRole="button"
|
|
161
|
-
accessibilityLabel={closeAccessibilityLabel}
|
|
162
|
-
testID={`${testID}-close`}
|
|
163
|
-
>
|
|
164
|
-
<AtomicIcon name={closeIcon} size="md" color="primary" />
|
|
98
|
+
<Modal visible={visible} animationType="none" transparent onRequestClose={onClose} testID={`${testID}-modal`}>
|
|
99
|
+
<View style={styles.overlay}>
|
|
100
|
+
<View style={[styles.container, { paddingBottom: insets.bottom + tokens.spacing.md }]}>
|
|
101
|
+
<View style={styles.header}>
|
|
102
|
+
<AtomicText style={styles.title}>{title || 'Select'}</AtomicText>
|
|
103
|
+
<TouchableOpacity onPress={onClose} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} accessibilityRole="button" accessibilityLabel={closeAccessibilityLabel} testID={`${testID}-close`}>
|
|
104
|
+
<AtomicIcon name={icons.close} size="md" color="primary" />
|
|
165
105
|
</TouchableOpacity>
|
|
166
106
|
</View>
|
|
167
107
|
|
|
168
|
-
{/* Search Bar */}
|
|
169
108
|
{searchable && (
|
|
170
|
-
<View style={
|
|
171
|
-
<AtomicIcon name={
|
|
172
|
-
<TextInput
|
|
173
|
-
|
|
174
|
-
onChangeText={onSearchChange}
|
|
175
|
-
placeholder={searchPlaceholder}
|
|
176
|
-
placeholderTextColor={tokens.colors.textSecondary}
|
|
177
|
-
style={searchInputStyles}
|
|
178
|
-
testID={`${testID}-search`}
|
|
179
|
-
/>
|
|
180
|
-
{searchQuery.length > 0 && (
|
|
181
|
-
<TouchableOpacity onPress={() => onSearchChange('')}>
|
|
182
|
-
<AtomicIcon name={closeIcon} size="sm" color="secondary" />
|
|
183
|
-
</TouchableOpacity>
|
|
184
|
-
)}
|
|
109
|
+
<View style={styles.search}>
|
|
110
|
+
<AtomicIcon name={icons.search} size="sm" color="secondary" />
|
|
111
|
+
<TextInput value={searchQuery} onChangeText={onSearchChange} placeholder={searchPlaceholder} placeholderTextColor={tokens.colors.textSecondary} style={styles.searchInput} testID={`${testID}-search`} />
|
|
112
|
+
{searchQuery.length > 0 && <TouchableOpacity onPress={() => onSearchChange('')}><AtomicIcon name={icons.close} size="sm" color="secondary" /></TouchableOpacity>}
|
|
185
113
|
</View>
|
|
186
114
|
)}
|
|
187
115
|
|
|
188
|
-
{/* Options List */}
|
|
189
116
|
{filteredOptions.length > 0 ? (
|
|
190
|
-
<FlatList
|
|
191
|
-
data={filteredOptions}
|
|
192
|
-
keyExtractor={(item: PickerOption) => item.value}
|
|
193
|
-
renderItem={renderOption}
|
|
194
|
-
showsVerticalScrollIndicator
|
|
195
|
-
testID={`${testID}-list`}
|
|
196
|
-
/>
|
|
117
|
+
<FlatList data={filteredOptions} keyExtractor={(item: PickerOption) => item.value} renderItem={renderOption} showsVerticalScrollIndicator testID={`${testID}-list`} />
|
|
197
118
|
) : (
|
|
198
|
-
<View style={
|
|
199
|
-
<AtomicIcon name={
|
|
200
|
-
<AtomicText style={
|
|
201
|
-
{emptyMessage}
|
|
202
|
-
</AtomicText>
|
|
119
|
+
<View style={styles.empty}>
|
|
120
|
+
<AtomicIcon name={icons.info} size="xl" color="secondary" />
|
|
121
|
+
<AtomicText style={styles.emptyText}>{emptyMessage}</AtomicText>
|
|
203
122
|
</View>
|
|
204
123
|
)}
|
|
205
124
|
</View>
|
|
206
125
|
</View>
|
|
207
126
|
</Modal>
|
|
208
127
|
);
|
|
209
|
-
});
|
|
128
|
+
});
|
|
@@ -43,7 +43,6 @@ export class PersistentDeviceIdService {
|
|
|
43
43
|
|
|
44
44
|
if (secureId) {
|
|
45
45
|
if (__DEV__) {
|
|
46
|
-
console.log('[PersistentDeviceIdService] Found secure device ID:', secureId);
|
|
47
46
|
}
|
|
48
47
|
cachedDeviceId = secureId;
|
|
49
48
|
return secureId;
|
|
@@ -54,14 +53,12 @@ export class PersistentDeviceIdService {
|
|
|
54
53
|
await this.secureRepo.set(newId);
|
|
55
54
|
|
|
56
55
|
if (__DEV__) {
|
|
57
|
-
console.log('[PersistentDeviceIdService] Created new persistent ID:', newId);
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
cachedDeviceId = newId;
|
|
61
59
|
return newId;
|
|
62
60
|
} catch (error) {
|
|
63
61
|
if (__DEV__) {
|
|
64
|
-
console.error('[PersistentDeviceIdService] Initialization failed, using fallback:', error);
|
|
65
62
|
}
|
|
66
63
|
const fallbackId = `fallback_${generateUUID()}`;
|
|
67
64
|
cachedDeviceId = fallbackId;
|
|
@@ -103,7 +100,6 @@ export class PersistentDeviceIdService {
|
|
|
103
100
|
cachedDeviceId = null;
|
|
104
101
|
initializationPromise = null;
|
|
105
102
|
if (__DEV__) {
|
|
106
|
-
console.log('[PersistentDeviceIdService] All device identifiers cleared');
|
|
107
103
|
}
|
|
108
104
|
} catch {
|
|
109
105
|
// Silent fail
|