@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
@@ -14,59 +14,12 @@ export interface AppNavigationResult {
14
14
  pop: (count?: number) => void;
15
15
  popToTop: () => void;
16
16
  canGoBack: () => boolean;
17
- getState: () => ReturnType<NavigationProp<ParamListBase>["getState"]>;
18
- getParent: () => NavigationProp<ParamListBase> | undefined;
17
+ getState: () => ReturnType<NavigationProp<ParamListBase>["getState"]> | null;
18
+ getParent: () => NavigationProp<ParamListBase> | null | undefined;
19
19
  /** Whether navigation is available (inside NavigationContainer) */
20
20
  isReady: boolean;
21
21
  }
22
22
 
23
- /**
24
- * Creates a no-op navigation object for use outside NavigationContainer
25
- */
26
- function createNoOpNavigation(): AppNavigationResult {
27
- return {
28
- navigate: () => {
29
- if (__DEV__) {
30
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
31
- }
32
- },
33
- push: () => {
34
- if (__DEV__) {
35
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
36
- }
37
- },
38
- goBack: () => {
39
- if (__DEV__) {
40
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
41
- }
42
- },
43
- reset: () => {
44
- if (__DEV__) {
45
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
46
- }
47
- },
48
- replace: () => {
49
- if (__DEV__) {
50
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
51
- }
52
- },
53
- pop: () => {
54
- if (__DEV__) {
55
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
56
- }
57
- },
58
- popToTop: () => {
59
- if (__DEV__) {
60
- console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
61
- }
62
- },
63
- canGoBack: () => false,
64
- getState: () => ({ key: 'root', index: 0, routeNames: [], history: [], routes: [], type: 'nav', stale: false }),
65
- getParent: () => undefined,
66
- isReady: false,
67
- };
68
- }
69
-
70
23
  /**
71
24
  * useAppNavigation Hook
72
25
  *
@@ -81,84 +34,101 @@ function createNoOpNavigation(): AppNavigationResult {
81
34
  * }
82
35
  */
83
36
  export function useAppNavigation(): AppNavigationResult {
84
- try {
85
- const navigation = useNavigation<NavigationProp<ParamListBase>>();
86
-
87
- const navigate = useCallback(
88
- (screen: string, params?: Record<string, unknown>) => {
89
- // Dynamic navigation: use CommonActions for type-safe arbitrary screen navigation
90
- navigation.dispatch(
91
- CommonActions.navigate({
92
- name: screen,
93
- params,
94
- })
95
- );
96
- },
97
- [navigation]
98
- );
37
+ // Always call hooks - no conditional calls
38
+ const navigation = useNavigation<NavigationProp<ParamListBase>>();
99
39
 
100
- const push = useCallback(
101
- (screen: string, params?: Record<string, unknown>) => {
102
- navigation.dispatch(StackActions.push(screen, params));
103
- },
104
- [navigation]
105
- );
40
+ // Check if navigation is ready
41
+ const isReady = Boolean(navigation);
106
42
 
107
- const goBack = useCallback(() => {
108
- if (navigation.canGoBack()) {
109
- navigation.goBack();
43
+ const navigate = useCallback(
44
+ (screen: string, params?: Record<string, unknown>) => {
45
+ if (!navigation) {
46
+ if (__DEV__) {
47
+ console.warn('[useAppNavigation] Navigation not ready. Component must be inside NavigationContainer.');
48
+ }
49
+ return;
110
50
  }
111
- }, [navigation]);
51
+ // Dynamic navigation: use CommonActions for type-safe arbitrary screen navigation
52
+ navigation.dispatch(
53
+ CommonActions.navigate({
54
+ name: screen,
55
+ params,
56
+ })
57
+ );
58
+ },
59
+ [navigation]
60
+ );
112
61
 
113
- const reset = useCallback(
114
- (screen: string, params?: Record<string, unknown>) => {
115
- navigation.reset({ index: 0, routes: [{ name: screen, params }] });
116
- },
117
- [navigation]
118
- );
62
+ const push = useCallback(
63
+ (screen: string, params?: Record<string, unknown>) => {
64
+ if (!navigation) return;
65
+ navigation.dispatch(StackActions.push(screen, params));
66
+ },
67
+ [navigation]
68
+ );
69
+
70
+ const goBack = useCallback(() => {
71
+ if (!navigation) return;
72
+ if (navigation.canGoBack()) {
73
+ navigation.goBack();
74
+ }
75
+ }, [navigation]);
76
+
77
+ const reset = useCallback(
78
+ (screen: string, params?: Record<string, unknown>) => {
79
+ if (!navigation) return;
80
+ navigation.reset({ index: 0, routes: [{ name: screen, params }] });
81
+ },
82
+ [navigation]
83
+ );
119
84
 
120
- const replace = useCallback(
121
- (screen: string, params?: Record<string, unknown>) => {
122
- navigation.dispatch(StackActions.replace(screen, params));
123
- },
124
- [navigation]
125
- );
85
+ const replace = useCallback(
86
+ (screen: string, params?: Record<string, unknown>) => {
87
+ if (!navigation) return;
88
+ navigation.dispatch(StackActions.replace(screen, params));
89
+ },
90
+ [navigation]
91
+ );
126
92
 
127
- const pop = useCallback(
128
- (count = 1) => {
129
- navigation.dispatch(StackActions.pop(count));
130
- },
131
- [navigation]
132
- );
93
+ const pop = useCallback(
94
+ (count = 1) => {
95
+ if (!navigation) return;
96
+ navigation.dispatch(StackActions.pop(count));
97
+ },
98
+ [navigation]
99
+ );
133
100
 
134
- const popToTop = useCallback(() => {
135
- navigation.dispatch(StackActions.popToTop());
136
- }, [navigation]);
101
+ const popToTop = useCallback(() => {
102
+ if (!navigation) return;
103
+ navigation.dispatch(StackActions.popToTop());
104
+ }, [navigation]);
137
105
 
138
- const canGoBack = useCallback(() => navigation.canGoBack(), [navigation]);
106
+ const canGoBackCallback = useCallback(() => {
107
+ return navigation ? navigation.canGoBack() : false;
108
+ }, [navigation]);
139
109
 
140
- const getState = useCallback(() => navigation.getState(), [navigation]);
110
+ const getState = useCallback(() => {
111
+ return navigation ? navigation.getState() : null;
112
+ }, [navigation]);
141
113
 
142
- const getParent = useCallback(() => navigation.getParent(), [navigation]);
114
+ const getParent = useCallback(() => {
115
+ return navigation ? navigation.getParent() : null;
116
+ }, [navigation]);
143
117
 
144
- return useMemo(
145
- () => ({
146
- navigate,
147
- push,
148
- goBack,
149
- reset,
150
- replace,
151
- pop,
152
- popToTop,
153
- canGoBack,
154
- getState,
155
- getParent,
156
- isReady: true,
157
- }),
158
- [navigate, push, goBack, reset, replace, pop, popToTop, canGoBack, getState, getParent]
159
- );
160
- } catch (error) {
161
- // Navigation not ready - return no-op navigation
162
- return createNoOpNavigation();
163
- }
118
+ return useMemo(
119
+ () => ({
120
+ navigate,
121
+ push,
122
+ goBack,
123
+ reset,
124
+ replace,
125
+ pop,
126
+ popToTop,
127
+ canGoBack: canGoBackCallback,
128
+ getState,
129
+ getParent,
130
+ isReady,
131
+ }),
132
+ [navigate, push, goBack, reset, replace, pop, popToTop, canGoBackCallback, getState, getParent, isReady]
133
+ );
164
134
  }
@@ -1,5 +1,5 @@
1
1
  import { useRoute } from "@react-navigation/native";
2
- import type { RouteProp, ParamListBase } from "@react-navigation/native";
2
+ import type { RouteProp } from "@react-navigation/native";
3
3
  import { useMemo } from "react";
4
4
 
5
5
  /**
@@ -16,32 +16,31 @@ import { useMemo } from "react";
16
16
  * }
17
17
  * ```
18
18
  */
19
- export function useAppRoute(): any {
20
- try {
21
- const route = useRoute();
22
- return useMemo(
23
- () => ({
24
- ...route,
25
- isReady: true,
26
- }),
27
- [route]
28
- );
29
- } catch (error) {
30
- // Route not ready - return empty route
31
- if (__DEV__) {
32
- console.warn('[useAppRoute] Route not ready. Component must be inside NavigationContainer.');
33
- }
34
- return useMemo(
35
- () => ({
36
- key: '',
37
- name: '',
38
- params: undefined,
39
- path: undefined,
40
- isReady: false,
41
- }),
42
- []
43
- );
44
- }
19
+ export interface AppRouteResult<T = unknown> {
20
+ key: string;
21
+ name: string;
22
+ params: T | undefined;
23
+ path: string | undefined;
24
+ isReady: boolean;
25
+ }
26
+
27
+ export function useAppRoute<T = unknown>(): AppRouteResult<T> {
28
+ // Always call hooks - no conditional calls
29
+ const route = useRoute<any>();
30
+
31
+ // Check if route is ready
32
+ const isReady = Boolean(route);
33
+
34
+ return useMemo(
35
+ () => ({
36
+ key: route?.key ?? '',
37
+ name: route?.name ?? '',
38
+ params: route?.params as T | undefined,
39
+ path: route?.path ?? undefined,
40
+ isReady,
41
+ }),
42
+ [route, isReady]
43
+ );
45
44
  }
46
45
 
47
46
  export type { RouteProp };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Chat Message Entity
3
+ *
4
+ * Defines a message in the chat interface
5
+ */
6
+
7
+ export interface ChatMessage {
8
+ /** Message text content */
9
+ text: string;
10
+
11
+ /** Whether this is a user message or bot message */
12
+ isUser: boolean;
13
+
14
+ /** Whether this message should be highlighted */
15
+ isImportant?: boolean;
16
+
17
+ /** Optional delay before showing this message */
18
+ delay?: number;
19
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Chat Step Entity
3
+ *
4
+ * Defines a single step in chat-based onboarding flow
5
+ * Generic and reusable across all apps
6
+ */
7
+
8
+ export type ChatMascotState = "idle" | "thinking" | "reacting" | "excited" | "speaking";
9
+
10
+ export interface ChatOption {
11
+ /** Display label for the option */
12
+ label: string;
13
+
14
+ /** Value to be stored when selected */
15
+ value: string;
16
+
17
+ /** Next step ID to navigate to */
18
+ next: string;
19
+
20
+ /** Optional icon name (from icon library) */
21
+ icon?: string;
22
+ }
23
+
24
+ export interface ChatStep {
25
+ /** Unique step identifier */
26
+ id: string;
27
+
28
+ /** Messages to display in chat bubbles */
29
+ messages: string[];
30
+
31
+ /** Optional mascot animation state */
32
+ mascotState?: ChatMascotState;
33
+
34
+ /** Delay before showing this step (ms) */
35
+ delay?: number;
36
+
37
+ /** Available options for user to select */
38
+ options?: ChatOption[];
39
+
40
+ /** Auto-advance to next step after delay */
41
+ autoNext?: string;
42
+
43
+ /** Manual next step ID (for input steps) */
44
+ next?: string;
45
+
46
+ /** Mark as completion step */
47
+ isComplete?: boolean;
48
+
49
+ /** Show persona summary card */
50
+ showPersonaSummary?: boolean;
51
+
52
+ /** Show insights section */
53
+ showInsights?: boolean;
54
+
55
+ /** Show matches section */
56
+ showMatches?: boolean;
57
+
58
+ /** Show phase visualizer */
59
+ showPhaseVisualizer?: boolean;
60
+
61
+ /** Show name input field */
62
+ isNameInput?: boolean;
63
+
64
+ /** Allow skipping name input if empty */
65
+ skipIfEmpty?: boolean;
66
+ }
67
+
68
+ /**
69
+ * Chat Onboarding Flow
70
+ * Record of step ID to step configuration
71
+ */
72
+ export type ChatOnboardingFlow = Record<string, ChatStep>;
@@ -39,6 +39,13 @@ export type {
39
39
  } from "./domain/entities/OnboardingQuestion";
40
40
  export type { OnboardingUserData } from "./domain/entities/OnboardingUserData";
41
41
 
42
+ // =============================================================================
43
+ // CHAT ONBOARDING - Domain Entities
44
+ // =============================================================================
45
+
46
+ export type { ChatStep, ChatOption, ChatOnboardingFlow, ChatMascotState } from "./domain/entities/ChatStep";
47
+ export type { ChatMessage } from "./domain/entities/ChatMessage";
48
+
42
49
  // =============================================================================
43
50
  // INFRASTRUCTURE LAYER - Storage and Hooks
44
51
  // =============================================================================
@@ -59,6 +66,15 @@ export {
59
66
  type UseOnboardingContainerStyleProps,
60
67
  type UseOnboardingContainerStyleReturn,
61
68
  } from "./presentation/hooks/useOnboardingContainerStyle";
69
+ export {
70
+ useChatAnimations,
71
+ type UseChatAnimationsReturn,
72
+ } from "./infrastructure/hooks/useChatAnimations";
73
+ export {
74
+ useChatOnboarding,
75
+ type UseChatOnboardingOptions,
76
+ type UseChatOnboardingReturn,
77
+ } from "./presentation/hooks/useChatOnboarding";
62
78
 
63
79
  // =============================================================================
64
80
  // PRESENTATION LAYER - Components and Screens
@@ -103,5 +119,18 @@ export type { RatingQuestionProps } from "./presentation/components/questions/Ra
103
119
  export { OnboardingResetSetting } from "./presentation/components/OnboardingResetSetting";
104
120
  export type { OnboardingResetSettingProps } from "./presentation/components/OnboardingResetSetting";
105
121
 
122
+ // =============================================================================
123
+ // CHAT ONBOARDING - Presentation Components
124
+ // =============================================================================
125
+
126
+ export { ChatMessageComponent } from "./presentation/components/chat";
127
+ export type { ChatMessageProps } from "./presentation/components/chat";
128
+ export { ChatOptionButton } from "./presentation/components/chat";
129
+ export type { ChatOptionButtonProps } from "./presentation/components/chat";
130
+ export { TypingIndicator } from "./presentation/components/chat";
131
+ export type { TypingIndicatorProps } from "./presentation/components/chat";
132
+ export { ChatOnboardingScreen } from "./presentation/screens/ChatOnboardingScreen";
133
+ export type { ChatOnboardingScreenProps } from "./presentation/screens/ChatOnboardingScreen";
134
+
106
135
 
107
136
  export { useOnboardingFlow, type UseOnboardingFlowResult } from './hooks/useOnboardingFlow';
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Chat Animations Hook
3
+ *
4
+ * Lazy loads React Native Reanimated for chat UI animations
5
+ * Prevents unnecessary bundle size for apps not using chat onboarding
6
+ */
7
+
8
+ import { useEffect, useState, useRef } from "react";
9
+
10
+ export interface UseChatAnimationsReturn {
11
+ /** Reanimated Animated value from lazy load */
12
+ Animated: any | null;
13
+
14
+ /** Whether animations are ready */
15
+ isReady: boolean;
16
+
17
+ /** Create fade-in animation */
18
+ createFadeIn: (duration?: number) => any;
19
+
20
+ /** Create slide-up animation */
21
+ createSlideUp: (fromY?: number, duration?: number) => any;
22
+
23
+ /** Create typing cursor animation */
24
+ createTypingCursor: () => any;
25
+
26
+ /** Clean up animations */
27
+ cleanup: () => void;
28
+ }
29
+
30
+ /**
31
+ * Hook for lazy-loading chat animations
32
+ * Only loads Reanimated when actually used
33
+ */
34
+ export const useChatAnimations = (): UseChatAnimationsReturn => {
35
+ const [Animated, setAnimated] = useState<any>(null);
36
+ const [isReady, setIsReady] = useState(false);
37
+ const animationRefs = useRef<any[]>([]);
38
+
39
+ useEffect(() => {
40
+ let mounted = true;
41
+
42
+ // Lazy load Reanimated only when needed
43
+ const loadAnimations = async () => {
44
+ try {
45
+ // const reanimated = await import("@umituz/react-native-animation");
46
+
47
+ if (!mounted) return;
48
+
49
+ // TODO: Lazy load Reanimated when @umituz/react-native-animation is available
50
+ // For now, return empty
51
+ setAnimated(() => null);
52
+ setIsReady(true);
53
+
54
+ if (__DEV__) {
55
+ console.log("[useChatAnimations] Animations disabled (Reanimated not available)");
56
+ }
57
+ } catch (error) {
58
+ if (__DEV__) {
59
+ console.warn("[useChatAnimations] Failed to load Reanimated:", error);
60
+ }
61
+ setIsReady(false);
62
+ }
63
+ };
64
+
65
+ loadAnimations();
66
+
67
+ return () => {
68
+ mounted = false;
69
+ // Clean up animation refs
70
+ animationRefs.current.forEach((ref) => {
71
+ if (ref && typeof ref.cancel === "function") {
72
+ ref.cancel();
73
+ }
74
+ });
75
+ animationRefs.current = [];
76
+ };
77
+ }, []);
78
+
79
+ const createFadeIn = (duration = 300) => {
80
+ if (!Animated) return null;
81
+
82
+ try {
83
+ const { withTiming } = Animated;
84
+ const animation = withTiming(1, { duration });
85
+ animationRefs.current.push(animation);
86
+ return animation;
87
+ } catch {
88
+ return null;
89
+ }
90
+ };
91
+
92
+ const createSlideUp = (_fromY = 20, duration = 400) => {
93
+ if (!Animated) return null;
94
+
95
+ try {
96
+ const { withTiming, Easing } = Animated;
97
+ const animation = withTiming(0, {
98
+ duration,
99
+ easing: Easing.out(Easing.ease),
100
+ });
101
+ animationRefs.current.push(animation);
102
+ return animation;
103
+ } catch {
104
+ return null;
105
+ }
106
+ };
107
+
108
+ const createTypingCursor = () => {
109
+ if (!Animated) return null;
110
+
111
+ try {
112
+ const { withRepeat, withSequence, withTiming, Easing } = Animated;
113
+ const animation = withRepeat(
114
+ withSequence(
115
+ withTiming(1, { duration: 800, easing: Easing.inOut(Easing.ease) }),
116
+ withTiming(0, { duration: 800, easing: Easing.inOut(Easing.ease) })
117
+ ),
118
+ -1,
119
+ true
120
+ );
121
+ animationRefs.current.push(animation);
122
+ return animation;
123
+ } catch {
124
+ return null;
125
+ }
126
+ };
127
+
128
+ const cleanup = () => {
129
+ animationRefs.current.forEach((ref) => {
130
+ if (ref && typeof ref.cancel === "function") {
131
+ ref.cancel();
132
+ }
133
+ });
134
+ animationRefs.current = [];
135
+ };
136
+
137
+ return {
138
+ Animated,
139
+ isReady,
140
+ createFadeIn,
141
+ createSlideUp,
142
+ createTypingCursor,
143
+ cleanup,
144
+ };
145
+ };