@umituz/react-native-design-system 4.25.8 → 4.25.10
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 +1 -1
- package/src/atoms/AtomicAvatar.tsx +0 -5
- package/src/atoms/AtomicInput.tsx +0 -2
- package/src/atoms/AtomicProgress.tsx +0 -5
- package/src/atoms/AtomicTextArea.tsx +0 -4
- package/src/atoms/card/AtomicCard.tsx +111 -54
- package/src/atoms/input/types.ts +0 -2
- package/src/atoms/skeleton/AtomicSkeleton.tsx +2 -2
- package/src/image/presentation/components/ImageGallery.tsx +16 -15
- package/src/image/presentation/components/editor/StickerPickerSheet.tsx +4 -7
- package/src/index.ts +5 -0
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +0 -2
- package/src/loading/presentation/providers/LoadingProvider.tsx +13 -4
- package/src/molecules/SearchBar/SearchBar.tsx +0 -2
- package/src/molecules/SearchBar/SearchSuggestions.tsx +1 -1
- package/src/molecules/SearchBar/types.ts +0 -1
- package/src/molecules/StepHeader/StepHeader.tsx +1 -1
- package/src/molecules/avatar/Avatar.tsx +76 -71
- package/src/molecules/avatar/AvatarGroup.tsx +1 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +2 -3
- package/src/molecules/calendar/presentation/components/CalendarWeekdayHeader.tsx +1 -1
- package/src/molecules/countdown/components/Countdown.tsx +14 -11
- package/src/molecules/info-grid/InfoGrid.tsx +2 -2
- package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +2 -2
- package/src/onboarding/presentation/components/OnboardingBackground.tsx +63 -49
- package/src/onboarding/presentation/components/OnboardingSlide.tsx +2 -2
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +3 -1
- package/src/gallery/gallery-download.service.ts +0 -69
- package/src/gallery/gallery-save.service.ts +0 -80
- package/src/gallery/index.ts +0 -3
- package/src/gallery/types.ts +0 -11
- package/src/image/domain/entities/EditorTypes.ts +0 -23
- package/src/image/domain/entities/editor/EditorConfigTypes.ts +0 -35
- package/src/image/domain/entities/editor/EditorElementTypes.ts +0 -60
- package/src/image/domain/entities/editor/EditorFilterTypes.ts +0 -9
- package/src/image/domain/entities/editor/EditorLayerTypes.ts +0 -34
- package/src/image/domain/entities/editor/EditorStateTypes.ts +0 -35
- package/src/image/domain/entities/editor/EditorToolTypes.ts +0 -33
- package/src/image/infrastructure/services/ImageEditorService.ts +0 -134
- package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +0 -120
- package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +0 -63
- package/src/image/infrastructure/utils/LayerManager.ts +0 -65
- package/src/media/infrastructure/hooks/useGenericMediaGeneration.ts +0 -170
- package/src/molecules/ConfirmationModal.tsx +0 -42
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.types.ts +0 -64
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.utils.ts +0 -56
- package/src/molecules/calendar/infrastructure/storage/EventActions.ts +0 -140
- package/src/molecules/calendar/infrastructure/storage/NavigationActions.ts +0 -118
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +0 -185
- package/src/molecules/confirmation-modal/index.ts +0 -7
- package/src/molecules/listitem/index.ts +0 -6
- package/src/molecules/navigation/components/index.ts +0 -4
- package/src/molecules/navigation/utils/NavigationTheme.ts +0 -21
- package/src/presentation/utils/variants/compound.ts +0 -34
- package/src/services/api/ApiClient.ts +0 -180
- package/src/services/api/index.ts +0 -9
- package/src/services/api/types/ApiTypes.ts +0 -50
- package/src/services/api/utils/requestBuilder.ts +0 -92
- package/src/services/api/utils/responseHandler.ts +0 -130
- package/src/storage/cache/index.ts +0 -28
- package/src/theme/core/tokens/BorderRadius.ts +0 -16
- package/src/utilities/clipboard/ClipboardUtils.ts +0 -67
- package/src/utilities/clipboard/index.ts +0 -5
- package/src/utilities/index.ts +0 -6
- package/src/utilities/sharing/domain/entities/Share.ts +0 -104
- package/src/utilities/sharing/domain/entities/SharingUtils.ts +0 -111
- package/src/utilities/sharing/index.ts +0 -33
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +0 -165
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +0 -116
- package/src/utils/colorMapper.ts +0 -193
- package/src/utils/errors/adapters/CacheErrorAdapter.ts +0 -68
- package/src/utils/errors/adapters/ImageErrorAdapter.ts +0 -91
- package/src/utils/errors/adapters/StorageErrorAdapter.ts +0 -107
- package/src/utils/formatHelper.ts +0 -16
- package/src/utils/formatters/dateFormatter.ts +0 -64
- package/src/utils/formatters/numberFormatter.ts +0 -130
- package/src/utils/index.ts +0 -16
- package/src/utils/styleComposer.ts +0 -94
- package/src/utils/validationHelper.ts +0 -16
- package/src/utils/validators/dataValidators.ts +0 -111
- package/src/utils/validators/numericValidators.ts +0 -106
- package/src/utils/validators/stringValidators.ts +0 -85
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.10",
|
|
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",
|
|
@@ -154,8 +154,3 @@ export const AtomicAvatar: React.FC<AtomicAvatarProps> = ({
|
|
|
154
154
|
);
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
-
// =============================================================================
|
|
158
|
-
// EXPORTS
|
|
159
|
-
// =============================================================================
|
|
160
|
-
|
|
161
|
-
export default AtomicAvatar;
|
|
@@ -29,7 +29,6 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
29
29
|
returnKeyType,
|
|
30
30
|
onSubmitEditing,
|
|
31
31
|
blurOnSubmit,
|
|
32
|
-
autoFocus,
|
|
33
32
|
autoCapitalize = 'sentences',
|
|
34
33
|
autoCorrect = true,
|
|
35
34
|
autoComplete,
|
|
@@ -116,7 +115,6 @@ export const AtomicInput = React.forwardRef<React.ElementRef<typeof TextInput>,
|
|
|
116
115
|
returnKeyType={returnKeyType}
|
|
117
116
|
onSubmitEditing={onSubmitEditing}
|
|
118
117
|
blurOnSubmit={blurOnSubmit}
|
|
119
|
-
autoFocus={autoFocus}
|
|
120
118
|
autoCapitalize={autoCapitalize}
|
|
121
119
|
autoCorrect={autoCorrect}
|
|
122
120
|
autoComplete={autoComplete}
|
|
@@ -144,8 +144,3 @@ const styles = StyleSheet.create({
|
|
|
144
144
|
},
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
// =============================================================================
|
|
148
|
-
// EXPORTS
|
|
149
|
-
// =============================================================================
|
|
150
|
-
|
|
151
|
-
export default AtomicProgress;
|
|
@@ -30,8 +30,6 @@ export interface AtomicTextAreaProps {
|
|
|
30
30
|
style?: StyleProp<ViewStyle>;
|
|
31
31
|
/** Input text style */
|
|
32
32
|
inputStyle?: StyleProp<TextStyle>;
|
|
33
|
-
/** Auto focus */
|
|
34
|
-
autoFocus?: boolean;
|
|
35
33
|
/** Return key type */
|
|
36
34
|
returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
|
|
37
35
|
/** Callback when submit button is pressed */
|
|
@@ -60,7 +58,6 @@ export const AtomicTextArea = forwardRef<React.ElementRef<typeof TextInput>, Ato
|
|
|
60
58
|
disabled = false,
|
|
61
59
|
style,
|
|
62
60
|
inputStyle,
|
|
63
|
-
autoFocus,
|
|
64
61
|
returnKeyType,
|
|
65
62
|
onSubmitEditing,
|
|
66
63
|
blurOnSubmit,
|
|
@@ -92,7 +89,6 @@ export const AtomicTextArea = forwardRef<React.ElementRef<typeof TextInput>, Ato
|
|
|
92
89
|
numberOfLines={lineCount}
|
|
93
90
|
multiline
|
|
94
91
|
editable={!disabled}
|
|
95
|
-
autoFocus={autoFocus}
|
|
96
92
|
returnKeyType={returnKeyType}
|
|
97
93
|
onSubmitEditing={onSubmitEditing}
|
|
98
94
|
blurOnSubmit={blurOnSubmit}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AtomicCard
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Unified card component that handles base card states, media, info, and glowing effects.
|
|
5
5
|
* Replaces InfoCard, MediaCard, and GlowingCard molecules.
|
|
6
6
|
*/
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
View,
|
|
11
11
|
Pressable,
|
|
12
12
|
type GestureResponderEvent,
|
|
13
|
+
type StyleProp,
|
|
14
|
+
type TextStyle,
|
|
13
15
|
} from 'react-native';
|
|
14
16
|
import { AtomicImage } from '../image/AtomicImage';
|
|
15
17
|
import { AtomicText } from '../AtomicText';
|
|
@@ -19,66 +21,45 @@ import { getCardVariantStyles, cardStyles } from './styles/cardStyles';
|
|
|
19
21
|
import { getCardPadding } from './configs/cardPaddingConfig';
|
|
20
22
|
import type { AtomicCardProps } from './types';
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
interface CardContentProps {
|
|
25
|
+
badge?: string;
|
|
26
|
+
image?: AtomicCardProps['image'];
|
|
27
|
+
imageAspectRatio: number;
|
|
28
|
+
selected: boolean;
|
|
29
|
+
checkCircleIcon: string;
|
|
30
|
+
paddingValue: number;
|
|
31
|
+
title?: string;
|
|
32
|
+
subtitle?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
leftIcon?: string;
|
|
35
|
+
rightIcon?: string;
|
|
36
|
+
titleStyle?: StyleProp<TextStyle>;
|
|
37
|
+
subtitleStyle?: StyleProp<TextStyle>;
|
|
38
|
+
descriptionStyle?: StyleProp<TextStyle>;
|
|
39
|
+
children?: React.ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CardContent: React.FC<CardContentProps> = ({
|
|
43
|
+
badge,
|
|
44
|
+
image,
|
|
45
|
+
imageAspectRatio,
|
|
46
|
+
selected,
|
|
47
|
+
checkCircleIcon,
|
|
48
|
+
paddingValue,
|
|
26
49
|
title,
|
|
27
50
|
subtitle,
|
|
28
51
|
description,
|
|
29
|
-
image,
|
|
30
|
-
imageAspectRatio = 1.6,
|
|
31
|
-
badge,
|
|
32
52
|
leftIcon,
|
|
33
53
|
rightIcon,
|
|
34
|
-
selected = false,
|
|
35
|
-
glowColor,
|
|
36
|
-
glowIntensity = 1,
|
|
37
|
-
onPress,
|
|
38
|
-
disabled = false,
|
|
39
|
-
style,
|
|
40
54
|
titleStyle,
|
|
41
55
|
subtitleStyle,
|
|
42
56
|
descriptionStyle,
|
|
43
|
-
|
|
57
|
+
children,
|
|
44
58
|
}) => {
|
|
45
59
|
const tokens = useAppDesignTokens();
|
|
46
|
-
const checkCircleIcon = useIconName('checkCircle');
|
|
47
|
-
|
|
48
|
-
const variantStyles = useMemo(() => {
|
|
49
|
-
const base = getCardVariantStyles(variant, tokens);
|
|
50
|
-
if (variant === 'glowing' && glowColor) {
|
|
51
|
-
return {
|
|
52
|
-
...base,
|
|
53
|
-
container: {
|
|
54
|
-
...base.container,
|
|
55
|
-
borderColor: glowColor,
|
|
56
|
-
borderWidth: 2 * glowIntensity,
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return base;
|
|
61
|
-
}, [variant, tokens, glowColor, glowIntensity]);
|
|
62
|
-
|
|
63
|
-
const paddingValue = getCardPadding(padding, tokens);
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
cardStyles.container,
|
|
67
|
-
{ borderRadius: tokens.borders.radius.lg },
|
|
68
|
-
variantStyles.container,
|
|
69
|
-
selected && { borderColor: tokens.colors.primary, borderWidth: 2 },
|
|
70
|
-
style,
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const handlePress = (event: GestureResponderEvent) => {
|
|
74
|
-
if (!disabled && onPress) {
|
|
75
|
-
onPress(event);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const renderContent = () => (
|
|
61
|
+
return (
|
|
80
62
|
<>
|
|
81
|
-
{/* Badge */}
|
|
82
63
|
{badge && (
|
|
83
64
|
<View style={[cardStyles.badge, { backgroundColor: tokens.colors.primary }]}>
|
|
84
65
|
<AtomicText type="labelSmall" color="onPrimary">
|
|
@@ -87,7 +68,6 @@ const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
|
87
68
|
</View>
|
|
88
69
|
)}
|
|
89
70
|
|
|
90
|
-
{/* Media */}
|
|
91
71
|
{image && (
|
|
92
72
|
<AtomicImage
|
|
93
73
|
source={typeof image === 'string' ? { uri: image } : image}
|
|
@@ -96,14 +76,12 @@ const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
|
96
76
|
/>
|
|
97
77
|
)}
|
|
98
78
|
|
|
99
|
-
{/* Selected Indicator */}
|
|
100
79
|
{selected && (
|
|
101
80
|
<View style={cardStyles.selectedOverlay}>
|
|
102
81
|
<AtomicIcon name={checkCircleIcon} color="primary" size="md" />
|
|
103
82
|
</View>
|
|
104
83
|
)}
|
|
105
84
|
|
|
106
|
-
{/* Text Content */}
|
|
107
85
|
<View style={{ padding: paddingValue }}>
|
|
108
86
|
{(title || leftIcon || rightIcon) && (
|
|
109
87
|
<View style={cardStyles.header}>
|
|
@@ -159,6 +137,85 @@ const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
|
159
137
|
</View>
|
|
160
138
|
</>
|
|
161
139
|
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
143
|
+
children,
|
|
144
|
+
variant = 'elevated',
|
|
145
|
+
padding = 'md',
|
|
146
|
+
title,
|
|
147
|
+
subtitle,
|
|
148
|
+
description,
|
|
149
|
+
image,
|
|
150
|
+
imageAspectRatio = 1.6,
|
|
151
|
+
badge,
|
|
152
|
+
leftIcon,
|
|
153
|
+
rightIcon,
|
|
154
|
+
selected = false,
|
|
155
|
+
glowColor,
|
|
156
|
+
glowIntensity = 1,
|
|
157
|
+
onPress,
|
|
158
|
+
disabled = false,
|
|
159
|
+
style,
|
|
160
|
+
titleStyle,
|
|
161
|
+
subtitleStyle,
|
|
162
|
+
descriptionStyle,
|
|
163
|
+
testID,
|
|
164
|
+
}) => {
|
|
165
|
+
const tokens = useAppDesignTokens();
|
|
166
|
+
const checkCircleIcon = useIconName('checkCircle');
|
|
167
|
+
|
|
168
|
+
const variantStyles = useMemo(() => {
|
|
169
|
+
const base = getCardVariantStyles(variant, tokens);
|
|
170
|
+
if (variant === 'glowing' && glowColor) {
|
|
171
|
+
return {
|
|
172
|
+
...base,
|
|
173
|
+
container: {
|
|
174
|
+
...base.container,
|
|
175
|
+
borderColor: glowColor,
|
|
176
|
+
borderWidth: 2 * glowIntensity,
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return base;
|
|
181
|
+
}, [variant, tokens, glowColor, glowIntensity]);
|
|
182
|
+
|
|
183
|
+
const paddingValue = getCardPadding(padding, tokens);
|
|
184
|
+
|
|
185
|
+
const containerStyle = [
|
|
186
|
+
cardStyles.container,
|
|
187
|
+
{ borderRadius: tokens.borders.radius.lg },
|
|
188
|
+
variantStyles.container,
|
|
189
|
+
selected && { borderColor: tokens.colors.primary, borderWidth: 2 },
|
|
190
|
+
style,
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const handlePress = (event: GestureResponderEvent) => {
|
|
194
|
+
if (!disabled && onPress) {
|
|
195
|
+
onPress(event);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const content = (
|
|
200
|
+
<CardContent
|
|
201
|
+
badge={badge}
|
|
202
|
+
image={image}
|
|
203
|
+
imageAspectRatio={imageAspectRatio}
|
|
204
|
+
selected={selected}
|
|
205
|
+
checkCircleIcon={checkCircleIcon}
|
|
206
|
+
paddingValue={paddingValue}
|
|
207
|
+
title={title}
|
|
208
|
+
subtitle={subtitle}
|
|
209
|
+
description={description}
|
|
210
|
+
leftIcon={leftIcon}
|
|
211
|
+
rightIcon={rightIcon}
|
|
212
|
+
titleStyle={titleStyle}
|
|
213
|
+
subtitleStyle={subtitleStyle}
|
|
214
|
+
descriptionStyle={descriptionStyle}
|
|
215
|
+
>
|
|
216
|
+
{children}
|
|
217
|
+
</CardContent>
|
|
218
|
+
);
|
|
162
219
|
|
|
163
220
|
if (onPress) {
|
|
164
221
|
return (
|
|
@@ -171,14 +228,14 @@ const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
|
171
228
|
]}
|
|
172
229
|
testID={testID}
|
|
173
230
|
>
|
|
174
|
-
{
|
|
231
|
+
{content}
|
|
175
232
|
</Pressable>
|
|
176
233
|
);
|
|
177
234
|
}
|
|
178
235
|
|
|
179
236
|
return (
|
|
180
237
|
<View style={containerStyle} testID={testID}>
|
|
181
|
-
{
|
|
238
|
+
{content}
|
|
182
239
|
</View>
|
|
183
240
|
);
|
|
184
241
|
};
|
package/src/atoms/input/types.ts
CHANGED
|
@@ -45,8 +45,6 @@ export interface AtomicInputProps {
|
|
|
45
45
|
onSubmitEditing?: () => void;
|
|
46
46
|
/** Blur on submit */
|
|
47
47
|
blurOnSubmit?: boolean;
|
|
48
|
-
/** Auto focus */
|
|
49
|
-
autoFocus?: boolean;
|
|
50
48
|
/** Auto-capitalize */
|
|
51
49
|
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
|
|
52
50
|
/** Auto-correct */
|
|
@@ -79,9 +79,9 @@ export const AtomicSkeleton: React.FC<AtomicSkeletonProps> = ({
|
|
|
79
79
|
|
|
80
80
|
const renderSkeletonItem = (index: number) => (
|
|
81
81
|
<View key={`skeleton-group-${index}`} style={styles.skeletonGroup}>
|
|
82
|
-
{skeletonConfigs.map((config
|
|
82
|
+
{skeletonConfigs.map((config) => (
|
|
83
83
|
<SkeletonItem
|
|
84
|
-
key={`skeleton-${index}-${
|
|
84
|
+
key={`skeleton-${index}-${config.width}-${config.height}`}
|
|
85
85
|
config={config}
|
|
86
86
|
baseColor={tokens.colors.surfaceSecondary}
|
|
87
87
|
multiplier={tokens.spacingMultiplier}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Presentation - Image Gallery Component
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* High-performance, premium image gallery using expo-image.
|
|
5
5
|
* Replaces slow standard image components for instant loading.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, { useCallback,
|
|
8
|
+
import React, { useCallback, useRef, useMemo } from 'react';
|
|
9
9
|
import { Modal, View, StyleSheet, FlatList, useWindowDimensions, type NativeSyntheticEvent, type NativeScrollEvent } from 'react-native';
|
|
10
10
|
import { Image } from 'expo-image';
|
|
11
11
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
@@ -35,7 +35,8 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|
|
35
35
|
}) => {
|
|
36
36
|
const insets = useSafeAreaInsets();
|
|
37
37
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = useWindowDimensions();
|
|
38
|
-
const
|
|
38
|
+
const currentIndexRef = useRef(index);
|
|
39
|
+
const [, forceRender] = React.useReducer((x: number) => x + 1, 0);
|
|
39
40
|
|
|
40
41
|
const styles = useMemo(() => StyleSheet.create({
|
|
41
42
|
container: {
|
|
@@ -63,23 +64,24 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|
|
63
64
|
}
|
|
64
65
|
}), [SCREEN_WIDTH, SCREEN_HEIGHT]);
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
67
|
+
if (visible) {
|
|
68
|
+
currentIndexRef.current = index;
|
|
69
|
+
}
|
|
69
70
|
|
|
70
71
|
const handleEdit = useCallback(async () => {
|
|
71
|
-
const currentImage = images[
|
|
72
|
+
const currentImage = images[currentIndexRef.current];
|
|
72
73
|
if (!currentImage || !onImageChange) return;
|
|
73
|
-
await onImageChange(currentImage.uri,
|
|
74
|
-
}, [images,
|
|
74
|
+
await onImageChange(currentImage.uri, currentIndexRef.current);
|
|
75
|
+
}, [images, onImageChange]);
|
|
75
76
|
|
|
76
77
|
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
77
78
|
const nextIndex = Math.round(event.nativeEvent.contentOffset.x / SCREEN_WIDTH);
|
|
78
|
-
if (nextIndex !==
|
|
79
|
-
|
|
79
|
+
if (nextIndex !== currentIndexRef.current) {
|
|
80
|
+
currentIndexRef.current = nextIndex;
|
|
80
81
|
onIndexChange?.(nextIndex);
|
|
82
|
+
forceRender();
|
|
81
83
|
}
|
|
82
|
-
}, [
|
|
84
|
+
}, [onIndexChange, SCREEN_WIDTH]);
|
|
83
85
|
|
|
84
86
|
const renderItem = useCallback(({ item }: { item: ImageViewerItem }) => (
|
|
85
87
|
<View style={styles.imageWrapper}>
|
|
@@ -98,7 +100,7 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|
|
98
100
|
index: i,
|
|
99
101
|
}), [SCREEN_WIDTH]);
|
|
100
102
|
|
|
101
|
-
if (!visible
|
|
103
|
+
if (!visible) return null;
|
|
102
104
|
|
|
103
105
|
return (
|
|
104
106
|
<Modal
|
|
@@ -112,7 +114,7 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|
|
112
114
|
<GalleryHeader
|
|
113
115
|
onClose={onDismiss}
|
|
114
116
|
onEdit={enableEditing ? handleEdit : undefined}
|
|
115
|
-
title={title || `${
|
|
117
|
+
title={title || `${currentIndexRef.current + 1} / ${images.length}`}
|
|
116
118
|
/>
|
|
117
119
|
|
|
118
120
|
<FlatList
|
|
@@ -130,7 +132,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|
|
130
132
|
/>
|
|
131
133
|
|
|
132
134
|
<View style={[styles.footer, { paddingBottom: Math.max(insets.bottom, 20) }]}>
|
|
133
|
-
{/* Potential for thumbnail strip or captions in future */}
|
|
134
135
|
</View>
|
|
135
136
|
</View>
|
|
136
137
|
</Modal>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Presentation - Sticker Picker Sheet
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React, { forwardRef
|
|
5
|
+
import React, { forwardRef } from 'react';
|
|
6
6
|
import { View, TouchableOpacity, ScrollView, Image, useWindowDimensions } from 'react-native';
|
|
7
7
|
import { BottomSheetModal } from '../../../../molecules/bottom-sheet/components/BottomSheetModal';
|
|
8
8
|
import type { BottomSheetModalRef } from '../../../../molecules/bottom-sheet/types/BottomSheet';
|
|
@@ -22,10 +22,7 @@ export const StickerPickerSheet = forwardRef<BottomSheetModalRef, StickerPickerS
|
|
|
22
22
|
const tokens = useAppDesignTokens();
|
|
23
23
|
const { width: SCREEN_WIDTH } = useWindowDimensions();
|
|
24
24
|
|
|
25
|
-
const stickerSize =
|
|
26
|
-
() => (SCREEN_WIDTH - 64) / 3,
|
|
27
|
-
[SCREEN_WIDTH]
|
|
28
|
-
);
|
|
25
|
+
const stickerSize = (SCREEN_WIDTH - 64) / 3;
|
|
29
26
|
|
|
30
27
|
return (
|
|
31
28
|
<BottomSheetModal ref={ref} snapPoints={snapPoints} onDismiss={onDismiss}>
|
|
@@ -36,9 +33,9 @@ export const StickerPickerSheet = forwardRef<BottomSheetModalRef, StickerPickerS
|
|
|
36
33
|
|
|
37
34
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
38
35
|
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: tokens.spacing.md }}>
|
|
39
|
-
{stickers.map((uri
|
|
36
|
+
{stickers.map((uri) => (
|
|
40
37
|
<TouchableOpacity
|
|
41
|
-
key={
|
|
38
|
+
key={uri}
|
|
42
39
|
onPress={() => onSelectSticker(uri)}
|
|
43
40
|
style={{
|
|
44
41
|
width: stickerSize,
|
package/src/index.ts
CHANGED
|
@@ -109,3 +109,8 @@ export * from "./storage";
|
|
|
109
109
|
// CAROUSEL EXPORTS
|
|
110
110
|
// =============================================================================
|
|
111
111
|
export * from "./carousel";
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// OFFLINE EXPORTS
|
|
115
|
+
// =============================================================================
|
|
116
|
+
export * from "./offline";
|
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
* Global loading provider with auto-detection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useEffect,
|
|
6
|
+
import React, { useEffect, useReducer, useRef } from 'react';
|
|
7
7
|
import { useIsFetching } from '@tanstack/react-query';
|
|
8
8
|
import { LoadingOverlay } from '../components/LoadingOverlay';
|
|
9
9
|
import type { LoadingProviderProps } from '../../domain/types/loading.types';
|
|
10
10
|
|
|
11
|
+
type LoadingAction = { type: 'SHOW' } | { type: 'HIDE' };
|
|
12
|
+
|
|
13
|
+
function loadingReducer(state: boolean, action: LoadingAction): boolean {
|
|
14
|
+
switch (action.type) {
|
|
15
|
+
case 'SHOW': return true;
|
|
16
|
+
case 'HIDE': return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
export const LoadingProvider: React.FC<LoadingProviderProps> = ({
|
|
12
21
|
children,
|
|
13
22
|
spinnerColor,
|
|
@@ -18,7 +27,7 @@ export const LoadingProvider: React.FC<LoadingProviderProps> = ({
|
|
|
18
27
|
minDisplayTime = 300,
|
|
19
28
|
}) => {
|
|
20
29
|
const isFetching = useIsFetching();
|
|
21
|
-
const [showFetchLoading,
|
|
30
|
+
const [showFetchLoading, dispatch] = useReducer(loadingReducer, false);
|
|
22
31
|
const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
23
32
|
const showTimeRef = useRef<number>(0);
|
|
24
33
|
|
|
@@ -31,13 +40,13 @@ export const LoadingProvider: React.FC<LoadingProviderProps> = ({
|
|
|
31
40
|
hideTimeoutRef.current = null;
|
|
32
41
|
}
|
|
33
42
|
showTimeRef.current = Date.now();
|
|
34
|
-
|
|
43
|
+
dispatch({ type: 'SHOW' });
|
|
35
44
|
} else if (showFetchLoading) {
|
|
36
45
|
const elapsed = Date.now() - showTimeRef.current;
|
|
37
46
|
const remaining = Math.max(0, minDisplayTime - elapsed);
|
|
38
47
|
|
|
39
48
|
hideTimeoutRef.current = setTimeout(() => {
|
|
40
|
-
|
|
49
|
+
dispatch({ type: 'HIDE' });
|
|
41
50
|
hideTimeoutRef.current = null;
|
|
42
51
|
}, remaining);
|
|
43
52
|
}
|
|
@@ -18,7 +18,6 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|
|
18
18
|
onFocus,
|
|
19
19
|
onBlur,
|
|
20
20
|
placeholder = 'Search...',
|
|
21
|
-
autoFocus = false,
|
|
22
21
|
loading = false,
|
|
23
22
|
disabled = false,
|
|
24
23
|
containerStyle,
|
|
@@ -67,7 +66,6 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|
|
67
66
|
onBlur={onBlur}
|
|
68
67
|
placeholder={placeholder}
|
|
69
68
|
placeholderTextColor={tokens.colors.textSecondary}
|
|
70
|
-
autoFocus={autoFocus}
|
|
71
69
|
editable={!disabled}
|
|
72
70
|
returnKeyType="search"
|
|
73
71
|
autoCapitalize="none"
|
|
@@ -102,7 +102,7 @@ export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
|
102
102
|
<View style={styles.stepIndicator}>
|
|
103
103
|
{Array.from({ length: cfg.totalSteps }, (_, i) => (
|
|
104
104
|
<View
|
|
105
|
-
key={i}
|
|
105
|
+
key={`step-${i}`}
|
|
106
106
|
style={[
|
|
107
107
|
styles.stepDot,
|
|
108
108
|
i + 1 <= cfg.currentStep!
|