@umituz/react-native-design-system 4.28.4 → 4.28.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/src/atoms/AtomicInput.tsx +2 -2
  3. package/src/index.ts +1 -1
  4. package/src/molecules/Divider/Divider.tsx +2 -3
  5. package/src/molecules/Divider/types.ts +22 -5
  6. package/src/molecules/StepHeader/StepHeader.constants.ts +48 -0
  7. package/src/molecules/StepHeader/StepHeader.tsx +29 -23
  8. package/src/molecules/StepProgress/StepProgress.constants.ts +23 -0
  9. package/src/molecules/StepProgress/StepProgress.tsx +9 -6
  10. package/src/molecules/avatar/Avatar.constants.ts +20 -2
  11. package/src/molecules/avatar/Avatar.tsx +5 -3
  12. package/src/molecules/avatar/Avatar.utils.ts +4 -4
  13. package/src/molecules/avatar/AvatarGroup.tsx +2 -2
  14. package/src/molecules/listitem/styles/listItemStyles.ts +2 -3
  15. package/src/molecules/navigation/hooks/useAppFocusEffect.ts +14 -11
  16. package/src/molecules/navigation/hooks/useAppIsFocused.ts +1 -2
  17. package/src/molecules/navigation/hooks/useAppNavigation.ts +88 -118
  18. package/src/molecules/navigation/hooks/useAppRoute.ts +26 -27
  19. package/src/onboarding/domain/entities/ChatMessage.ts +19 -0
  20. package/src/onboarding/domain/entities/ChatStep.ts +72 -0
  21. package/src/onboarding/index.ts +29 -0
  22. package/src/onboarding/infrastructure/hooks/useChatAnimations.ts +145 -0
  23. package/src/onboarding/presentation/components/chat/ChatMessage.tsx +166 -0
  24. package/src/onboarding/presentation/components/chat/ChatOptionButton.tsx +145 -0
  25. package/src/onboarding/presentation/components/chat/TypingIndicator.tsx +99 -0
  26. package/src/onboarding/presentation/components/chat/index.ts +12 -0
  27. package/src/onboarding/presentation/hooks/useChatOnboarding.ts +278 -0
  28. package/src/onboarding/presentation/screens/ChatOnboardingScreen.tsx +276 -0
  29. package/src/utils/index.ts +13 -0
  30. package/src/utils/responsiveUtils.ts +110 -0
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Chat Onboarding Screen
3
+ *
4
+ * Chat-based onboarding experience for user engagement
5
+ * Fully configurable flow, optional mascot, lazy-loaded animations
6
+ *
7
+ * Responsive Design:
8
+ * - Uses useSafeAreaInsets() from react-native-safe-area-context for proper device compatibility
9
+ * - Flexible flex-based layouts that adapt to all screen sizes
10
+ * - KeyboardAvoidingView for proper keyboard handling on iOS/Android
11
+ * - Percentage-based maxWidth in chat messages for responsive text layout
12
+ * - Proper insets integration with device and safe-area modules
13
+ *
14
+ * Compatible with:
15
+ * - @umituz/react-native-design-system/device (useDeviceInfo)
16
+ * - @umituz/react-native-design-system/safe-area (useSafeAreaInsets)
17
+ * - All screen sizes (iPhone, iPad, Android phones/tablets)
18
+ */
19
+
20
+ import React, { memo } from "react";
21
+ import {
22
+ View,
23
+ StyleSheet,
24
+ ScrollView,
25
+ TextInput,
26
+ TouchableOpacity,
27
+ KeyboardAvoidingView,
28
+ Platform,
29
+ ViewStyle,
30
+ Text,
31
+ } from "react-native";
32
+ import { useResponsive } from "../../../responsive/useResponsive";
33
+
34
+ import type { ChatStep } from "../../domain/entities/ChatStep";
35
+ import { useChatOnboarding } from "../hooks/useChatOnboarding";
36
+ import { ChatMessageComponent, ChatOptionButton, TypingIndicator } from "../components/chat";
37
+
38
+ export interface ChatOnboardingScreenProps {
39
+ /** Chat onboarding flow configuration */
40
+ flow: Record<string, ChatStep>;
41
+
42
+ /** Initial step ID */
43
+ initialStepId?: string;
44
+
45
+ /** Completion callback */
46
+ onComplete?: () => void;
47
+
48
+ /** Skip callback */
49
+ onSkip?: () => void;
50
+
51
+ /** Show mascot (default: false) */
52
+ showMascot?: boolean;
53
+
54
+ /** Mascot component to render */
55
+ renderMascot?: (state: string) => React.ReactNode;
56
+
57
+ /** Theme colors */
58
+ theme?: {
59
+ /** Background color */
60
+ background?: string;
61
+
62
+ /** Message background color */
63
+ messageBackground?: string;
64
+
65
+ /** Message text color */
66
+ messageText?: string;
67
+
68
+ /** User message background color */
69
+ userMessageBackground?: string;
70
+
71
+ /** User message text color */
72
+ userMessageText?: string;
73
+
74
+ /** Button background color */
75
+ buttonBackground?: string;
76
+
77
+ /** Button text color */
78
+ buttonText?: string;
79
+
80
+ /** Button border color */
81
+ buttonBorder?: string;
82
+ };
83
+
84
+ /** Custom styles */
85
+ style?: ViewStyle;
86
+
87
+ /** Delay between messages */
88
+ messageDelay?: number;
89
+
90
+ /** Render custom icon */
91
+ renderIcon?: (iconName: string) => React.ReactNode;
92
+
93
+ /** Storage key for progress persistence */
94
+ storageKey?: string;
95
+ }
96
+
97
+ /**
98
+ * Chat onboarding screen component
99
+ */
100
+ export const ChatOnboardingScreen = memo(
101
+ ({
102
+ flow,
103
+ initialStepId,
104
+ onComplete,
105
+ onSkip,
106
+ showMascot = false,
107
+ renderMascot,
108
+ theme = {},
109
+ style,
110
+ messageDelay = 500,
111
+ renderIcon,
112
+ }: ChatOnboardingScreenProps) => {
113
+ const responsive = useResponsive();
114
+
115
+ const {
116
+ currentStep,
117
+ messages,
118
+ showOptions,
119
+ isProcessing,
120
+ showTypingIndicator,
121
+ handleOptionSelect,
122
+ handleSubmitName,
123
+ } = useChatOnboarding({
124
+ flow,
125
+ initialStepId,
126
+ onComplete,
127
+ onSkip,
128
+ messageDelay,
129
+ });
130
+
131
+ const [nameInput, setNameInput] = React.useState("");
132
+
133
+ const handleNameSubmit = () => {
134
+ handleSubmitName(nameInput);
135
+ setNameInput("");
136
+ };
137
+
138
+ const containerStyle = [
139
+ styles.container,
140
+ {
141
+ backgroundColor: theme.background || "#FFFFFF",
142
+ paddingBottom: responsive.insets.bottom + 20,
143
+ paddingTop: responsive.insets.top + 20,
144
+ },
145
+ style,
146
+ ];
147
+
148
+ return (
149
+ <KeyboardAvoidingView
150
+ style={containerStyle}
151
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
152
+ keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 20}
153
+ >
154
+ {/* Mascot Section */}
155
+ {showMascot && renderMascot && currentStep?.mascotState && (
156
+ <View style={styles.mascotContainer}>{renderMascot(currentStep.mascotState)}</View>
157
+ )}
158
+
159
+ {/* Messages Section */}
160
+ <ScrollView
161
+ style={styles.messagesContainer}
162
+ contentContainerStyle={styles.messagesContent}
163
+ showsVerticalScrollIndicator={false}
164
+ >
165
+ {messages.map((msg, index) => (
166
+ <ChatMessageComponent
167
+ key={`${currentStep?.id}-${index}`}
168
+ message={msg}
169
+ backgroundColor={theme.messageBackground}
170
+ textColor={theme.messageText}
171
+ userBackgroundColor={theme.userMessageBackground}
172
+ userTextColor={theme.userMessageText}
173
+ />
174
+ ))}
175
+
176
+ {showTypingIndicator && (
177
+ <TypingIndicator
178
+ backgroundColor={theme.messageBackground}
179
+ dotColor={theme.messageText}
180
+ />
181
+ )}
182
+
183
+ {/* Options Section */}
184
+ {showOptions && currentStep?.options && (
185
+ <View style={styles.optionsContainer}>
186
+ {currentStep.options.map((option, index) => (
187
+ <ChatOptionButton
188
+ key={`${option.value}-${index}`}
189
+ option={option}
190
+ onPress={handleOptionSelect}
191
+ backgroundColor={theme.buttonBackground}
192
+ textColor={theme.buttonText}
193
+ borderColor={theme.buttonBorder}
194
+ disabled={isProcessing}
195
+ renderIcon={renderIcon}
196
+ />
197
+ ))}
198
+ </View>
199
+ )}
200
+
201
+ {/* Name Input Section */}
202
+ {currentStep?.isNameInput && (
203
+ <View style={styles.inputContainer}>
204
+ <TextInput
205
+ style={[styles.nameInput, { borderColor: theme.buttonBorder }]}
206
+ placeholder="Enter your name..."
207
+ placeholderTextColor={theme.messageText}
208
+ value={nameInput}
209
+ onChangeText={setNameInput}
210
+ onSubmitEditing={handleNameSubmit}
211
+ returnKeyType="done"
212
+ autoCapitalize="words"
213
+ />
214
+ <TouchableOpacity
215
+ style={[styles.submitButton, { backgroundColor: theme.buttonBackground }]}
216
+ onPress={handleNameSubmit}
217
+ disabled={isProcessing || !nameInput.trim()}
218
+ >
219
+ <Text style={[styles.submitButtonText, { color: theme.buttonText }]}>Send</Text>
220
+ </TouchableOpacity>
221
+ </View>
222
+ )}
223
+ </ScrollView>
224
+ </KeyboardAvoidingView>
225
+ );
226
+ }
227
+ );
228
+
229
+ ChatOnboardingScreen.displayName = "ChatOnboardingScreen";
230
+
231
+ const styles = StyleSheet.create({
232
+ container: {
233
+ flex: 1,
234
+ },
235
+ mascotContainer: {
236
+ alignItems: "center",
237
+ paddingVertical: 20,
238
+ },
239
+ messagesContainer: {
240
+ flex: 1,
241
+ paddingHorizontal: 16,
242
+ },
243
+ messagesContent: {
244
+ paddingTop: 10,
245
+ },
246
+ optionsContainer: {
247
+ marginTop: 16,
248
+ marginBottom: 16,
249
+ },
250
+ inputContainer: {
251
+ flexDirection: "row",
252
+ alignItems: "center",
253
+ marginTop: 12,
254
+ marginBottom: 16,
255
+ },
256
+ nameInput: {
257
+ flex: 1,
258
+ height: 48,
259
+ borderWidth: 1,
260
+ borderRadius: 12,
261
+ paddingHorizontal: 16,
262
+ fontSize: 16,
263
+ marginRight: 8,
264
+ },
265
+ submitButton: {
266
+ height: 48,
267
+ paddingHorizontal: 20,
268
+ borderRadius: 12,
269
+ justifyContent: "center",
270
+ alignItems: "center",
271
+ },
272
+ submitButtonText: {
273
+ fontSize: 16,
274
+ fontWeight: "600",
275
+ },
276
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Utils Module
3
+ *
4
+ * Centralized utility functions for the design system.
5
+ */
6
+
7
+ export {
8
+ calculateResponsiveSize,
9
+ calculateResponsiveSizes,
10
+ calculateResponsiveSizeSubtle,
11
+ calculateLineHeight,
12
+ createResponsiveSizes,
13
+ } from './responsiveUtils';
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Responsive Utility Functions
3
+ *
4
+ * Centralized responsive calculations to avoid code duplication.
5
+ * All components should use these utilities instead of manual calculations.
6
+ */
7
+
8
+ /**
9
+ * Calculate responsive size from base value
10
+ * Multiplies base size by spacingMultiplier and floors the result
11
+ *
12
+ * @param baseSize - Base size in pixels (e.g., 16, 24, 32)
13
+ * @param spacingMultiplier - Multiplier from design tokens (0.9, 1.0, 1.2)
14
+ * @returns Responsive size in pixels (floored for integer values)
15
+ *
16
+ * @example
17
+ * const responsive = useResponsive();
18
+ * const padding = calculateResponsiveSize(16, responsive.spacingMultiplier); // 14.4 → 14
19
+ */
20
+ export const calculateResponsiveSize = (
21
+ baseSize: number,
22
+ spacingMultiplier: number
23
+ ): number => {
24
+ return Math.floor(baseSize * spacingMultiplier);
25
+ };
26
+
27
+ /**
28
+ * Calculate multiple responsive sizes at once
29
+ * Useful for component configs with multiple size properties
30
+ *
31
+ * @param sizes - Object with base sizes
32
+ * @param spacingMultiplier - Multiplier from design tokens
33
+ * @returns Object with responsive sizes
34
+ *
35
+ * @example
36
+ * const baseConfig = { padding: 16, margin: 24, fontSize: 14 };
37
+ * const responsiveConfig = calculateResponsiveSizes(baseConfig, spacingMultiplier);
38
+ * // { padding: 14, margin: 21, fontSize: 12 }
39
+ */
40
+ export const calculateResponsiveSizes = <T extends Record<string, number>>(
41
+ sizes: T,
42
+ spacingMultiplier: number
43
+ ): { [K in keyof T]: number } => {
44
+ const result = {} as { [K in keyof T]: number };
45
+
46
+ for (const key in sizes) {
47
+ if (Object.prototype.hasOwnProperty.call(sizes, key)) {
48
+ result[key] = calculateResponsiveSize(sizes[key], spacingMultiplier);
49
+ }
50
+ }
51
+
52
+ return result;
53
+ };
54
+
55
+ /**
56
+ * Calculate responsive size with subtle scaling
57
+ * For values that shouldn't scale as much (e.g., borderWidth, borderRadius)
58
+ * Uses 0.8x of the spacing multiplier
59
+ *
60
+ * @param baseSize - Base size in pixels
61
+ * @param spacingMultiplier - Multiplier from design tokens
62
+ * @returns Responsive size with subtle scaling
63
+ *
64
+ * @example
65
+ * const borderWidth = calculateResponsiveSizeSubtle(2, spacingMultiplier); // 2 → 2 (small tablets)
66
+ */
67
+ export const calculateResponsiveSizeSubtle = (
68
+ baseSize: number,
69
+ spacingMultiplier: number
70
+ ): number => {
71
+ const subtleMultiplier = Math.max(1, spacingMultiplier * 0.8);
72
+ return Math.floor(baseSize * subtleMultiplier);
73
+ };
74
+
75
+ /**
76
+ * Calculate responsive line height from font size
77
+ * Standard 1.5x ratio for readability
78
+ *
79
+ * @param fontSize - Font size in pixels
80
+ * @param spacingMultiplier - Multiplier from design tokens
81
+ * @returns Line height in pixels
82
+ *
83
+ * @example
84
+ * const lineHeight = calculateLineHeight(16, spacingMultiplier); // 24 → 20
85
+ */
86
+ export const calculateLineHeight = (
87
+ fontSize: number,
88
+ spacingMultiplier: number
89
+ ): number => {
90
+ return Math.floor(fontSize * spacingMultiplier * 1.5);
91
+ };
92
+
93
+ /**
94
+ * Create responsive StyleSheet values
95
+ * Converts an object with base sizes to responsive sizes
96
+ *
97
+ * @param baseStyles - Object with base style values
98
+ * @param spacingMultiplier - Multiplier from design tokens
99
+ * @returns Responsive style values
100
+ *
101
+ * @example
102
+ * const baseSizes = { width: 100, height: 50, padding: 16 };
103
+ * const responsiveSizes = createResponsiveSizes(baseSizes, spacingMultiplier);
104
+ */
105
+ export const createResponsiveSizes = <T extends Record<string, number>>(
106
+ baseSizes: T,
107
+ spacingMultiplier: number
108
+ ): T => {
109
+ return calculateResponsiveSizes(baseSizes, spacingMultiplier) as T;
110
+ };