@umituz/react-native-mascot 1.0.10 → 1.2.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-mascot",
3
- "version": "1.0.10",
3
+ "version": "1.2.0",
4
4
  "description": "Interactive mascot system for React Native apps - Customizable animated characters with Lottie and SVG support, mood system, and easy integration",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -49,19 +49,23 @@
49
49
  "url": "https://github.com/umituz/react-native-mascot"
50
50
  },
51
51
  "dependencies": {
52
- "lottie-react-native": "^7.3.4",
53
- "react-native-reanimated": "^3.16.2"
52
+ "lottie-react-native": "^7.3.4"
54
53
  },
55
54
  "peerDependencies": {
55
+ "@umituz/react-native-animation": "*",
56
56
  "@umituz/react-native-design-system": "*",
57
57
  "expo": ">=54.0.0",
58
58
  "react": ">=19.0.0",
59
59
  "react-native": "*",
60
+ "react-native-reanimated": ">=3.0.0",
60
61
  "react-native-svg": ">=15.0.0"
61
62
  },
62
63
  "peerDependenciesMeta": {
63
64
  "@umituz/react-native-design-system": {
64
65
  "optional": true
66
+ },
67
+ "react-native-reanimated": {
68
+ "optional": false
65
69
  }
66
70
  },
67
71
  "devDependencies": {
@@ -0,0 +1,285 @@
1
+ /**
2
+ * SimpleMascot Component
3
+ *
4
+ * A simplified, configurable mascot component that works across different apps.
5
+ * Uses Reanimated animations and accepts dynamic theme colors and image sources.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { SimpleMascot } from '@umituz/react-native-mascot';
10
+ *
11
+ * <SimpleMascot
12
+ * source={require('../assets/mascot.png')}
13
+ * state="thinking"
14
+ * size="large"
15
+ * theme={{
16
+ * primary: '#FF6B6B',
17
+ * glow: 'rgba(255, 107, 107, 0.15)',
18
+ * }}
19
+ * />
20
+ * ```
21
+ */
22
+
23
+ import React, { useEffect, memo } from 'react';
24
+ import { View, Image, StyleSheet, ViewStyle, ImageSource } from 'react-native';
25
+ import Animated, {
26
+ useSharedValue,
27
+ useAnimatedStyle,
28
+ withTiming,
29
+ withRepeat,
30
+ withSequence,
31
+ Easing,
32
+ } from '@umituz/react-native-animation';
33
+
34
+ import type { MascotState } from './types';
35
+
36
+ export interface SimpleMascotProps {
37
+ /** Image source for the mascot (require() or URI) */
38
+ source: ImageSource;
39
+
40
+ /** Animation state */
41
+ state?: MascotState;
42
+
43
+ /** Size preset or custom size */
44
+ size?: number | 'small' | 'medium' | 'large';
45
+
46
+ /** Theme colors */
47
+ theme?: {
48
+ /** Primary glow color */
49
+ primary?: string;
50
+ /** Glow effect color (with alpha) */
51
+ glow?: string;
52
+ /** Message background color */
53
+ background?: string;
54
+ /** Message text color */
55
+ text?: string;
56
+ };
57
+
58
+ /** Optional message to display */
59
+ message?: string;
60
+
61
+ /** Enable/disable animations */
62
+ animate?: boolean;
63
+
64
+ /** Additional styles */
65
+ style?: ViewStyle;
66
+
67
+ /** Test ID for testing */
68
+ testID?: string;
69
+ }
70
+
71
+ const SIZE_PRESETS: Record<string, number> = {
72
+ small: 60,
73
+ medium: 100,
74
+ large: 140,
75
+ };
76
+
77
+ function SimpleMascotComponent({
78
+ source,
79
+ state = 'idle',
80
+ size = 'medium',
81
+ theme = {},
82
+ message,
83
+ animate = true,
84
+ style,
85
+ testID = 'simple-mascot',
86
+ }: SimpleMascotProps) {
87
+ const translateY = useSharedValue(0);
88
+ const scale = useSharedValue(1);
89
+ const rotate = useSharedValue(0);
90
+
91
+ // Resolve size
92
+ const imageSize = typeof size === 'number' ? size : SIZE_PRESETS[size] || SIZE_PRESETS.medium;
93
+
94
+ // Resolve theme colors with defaults
95
+ const primaryColor = theme.primary || '#FF6B6B';
96
+ const glowColor = theme.glow || `${primaryColor}26`; // 15% opacity
97
+ const backgroundColor = theme.background || 'rgba(30, 30, 30, 0.9)';
98
+ const textColor = theme.text || '#FFFFFF';
99
+
100
+ useEffect(() => {
101
+ // Reset animations
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- Reanimated shared values are designed to be mutable
103
+ translateY.value = 0;
104
+ // eslint-disable-next-line react-hooks/exhaustive-deps
105
+ scale.value = 1;
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ rotate.value = 0;
108
+
109
+ if (!animate) return;
110
+
111
+ switch (state) {
112
+ case 'idle':
113
+ // Gentle floating animation
114
+ translateY.value = withRepeat(
115
+ withSequence(
116
+ withTiming(-6, { duration: 1500, easing: Easing.inOut(Easing.ease) }),
117
+ withTiming(0, { duration: 1500, easing: Easing.inOut(Easing.ease) }),
118
+ ),
119
+ -1,
120
+ true,
121
+ );
122
+ scale.value = withRepeat(
123
+ withSequence(
124
+ withTiming(1.03, { duration: 2000, easing: Easing.inOut(Easing.ease) }),
125
+ withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }),
126
+ ),
127
+ -1,
128
+ true,
129
+ );
130
+ break;
131
+
132
+ case 'thinking':
133
+ // Wiggle/twitch animation
134
+ rotate.value = withRepeat(
135
+ withSequence(
136
+ withTiming(-5, { duration: 200, easing: Easing.inOut(Easing.ease) }),
137
+ withTiming(5, { duration: 200, easing: Easing.inOut(Easing.ease) }),
138
+ withTiming(-5, { duration: 200, easing: Easing.inOut(Easing.ease) }),
139
+ withTiming(0, { duration: 200, easing: Easing.inOut(Easing.ease) }),
140
+ ),
141
+ -1,
142
+ false,
143
+ );
144
+ break;
145
+
146
+ case 'reacting':
147
+ // Quick pulse + rotation
148
+ scale.value = withSequence(
149
+ withTiming(1.15, { duration: 150, easing: Easing.out(Easing.ease) }),
150
+ withTiming(1, { duration: 150, easing: Easing.inOut(Easing.ease) }),
151
+ );
152
+ rotate.value = withSequence(
153
+ withTiming(-10, { duration: 100, easing: Easing.out(Easing.ease) }),
154
+ withTiming(10, { duration: 100, easing: Easing.out(Easing.ease) }),
155
+ withTiming(0, { duration: 100, easing: Easing.inOut(Easing.ease) }),
156
+ );
157
+ break;
158
+
159
+ case 'excited':
160
+ // Bouncy jump animation
161
+ translateY.value = withRepeat(
162
+ withSequence(
163
+ withTiming(-15, { duration: 400, easing: Easing.out(Easing.ease) }),
164
+ withTiming(0, { duration: 300, easing: Easing.in(Easing.ease) }),
165
+ withTiming(-10, { duration: 300, easing: Easing.out(Easing.ease) }),
166
+ withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) }),
167
+ ),
168
+ -1,
169
+ false,
170
+ );
171
+ scale.value = withRepeat(
172
+ withSequence(
173
+ withTiming(1.08, { duration: 400, easing: Easing.out(Easing.ease) }),
174
+ withTiming(1, { duration: 300, easing: Easing.in(Easing.ease) }),
175
+ ),
176
+ -1,
177
+ false,
178
+ );
179
+ break;
180
+
181
+ case 'speaking':
182
+ // Subtle breathing animation
183
+ scale.value = withRepeat(
184
+ withSequence(
185
+ withTiming(1.04, { duration: 800, easing: Easing.inOut(Easing.ease) }),
186
+ withTiming(1, { duration: 800, easing: Easing.inOut(Easing.ease) }),
187
+ ),
188
+ -1,
189
+ true,
190
+ );
191
+ translateY.value = withRepeat(
192
+ withSequence(
193
+ withTiming(-3, { duration: 1000, easing: Easing.inOut(Easing.ease) }),
194
+ withTiming(0, { duration: 1000, easing: Easing.inOut(Easing.ease) }),
195
+ ),
196
+ -1,
197
+ true,
198
+ );
199
+ break;
200
+
201
+ default:
202
+ break;
203
+ }
204
+ }, [animate, state, translateY, scale, rotate]);
205
+
206
+ const animatedStyle = useAnimatedStyle(() => ({
207
+ transform: [
208
+ { translateY: translateY.value },
209
+ { scale: scale.value },
210
+ { rotate: `${rotate.value}deg` },
211
+ ],
212
+ }));
213
+
214
+ return (
215
+ <View style={[styles.container, style]} testID={testID}>
216
+ <Animated.View style={animatedStyle}>
217
+ <View
218
+ style={[
219
+ styles.glowContainer,
220
+ { width: imageSize + 20, height: imageSize + 20 },
221
+ ]}
222
+ >
223
+ <View
224
+ style={[
225
+ styles.glow,
226
+ { width: imageSize + 20, height: imageSize + 20, backgroundColor: glowColor },
227
+ ]}
228
+ />
229
+ <Image
230
+ source={source}
231
+ style={[
232
+ styles.mascotImage,
233
+ { width: imageSize, height: imageSize },
234
+ ]}
235
+ resizeMode="contain"
236
+ />
237
+ </View>
238
+ </Animated.View>
239
+ {message ? (
240
+ <View style={[styles.messageContainer, { backgroundColor }]}>
241
+ <SimpleMascotText style={{ color: textColor }}>{message}</SimpleMascotText>
242
+ </View>
243
+ ) : null}
244
+ </View>
245
+ );
246
+ }
247
+
248
+ SimpleMascotComponent.displayName = 'SimpleMascot';
249
+
250
+ const SimpleMascotText = memo(({ style, children }: { style: any; children: React.ReactNode }) => (
251
+ <View>
252
+ {typeof children === 'string' ? (
253
+ <Animated.Text style={style}>{children}</Animated.Text>
254
+ ) : (
255
+ children
256
+ )}
257
+ </View>
258
+ ));
259
+
260
+ export const SimpleMascot = memo(SimpleMascotComponent);
261
+
262
+ const styles = StyleSheet.create({
263
+ container: {
264
+ alignItems: 'center',
265
+ },
266
+ glow: {
267
+ borderRadius: 9999, // Circular
268
+ position: 'absolute',
269
+ },
270
+ glowContainer: {
271
+ alignItems: 'center',
272
+ justifyContent: 'center',
273
+ position: 'relative',
274
+ },
275
+ mascotImage: {
276
+ borderRadius: 9999, // Circular
277
+ },
278
+ messageContainer: {
279
+ borderRadius: 12,
280
+ marginTop: 12,
281
+ maxWidth: 280,
282
+ paddingHorizontal: 16,
283
+ paddingVertical: 8,
284
+ },
285
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Mascot Types
3
+ * Shared type definitions for mascot components
4
+ */
5
+
6
+ export type MascotState = 'idle' | 'thinking' | 'reacting' | 'excited' | 'speaking';
7
+
8
+ export type MascotSize = 'small' | 'medium' | 'large' | number;
9
+
10
+ export interface MascotTheme {
11
+ /** Primary color (used for glow) */
12
+ primary?: string;
13
+ /** Glow effect color with alpha */
14
+ glow?: string;
15
+ /** Message bubble background */
16
+ background?: string;
17
+ /** Message text color */
18
+ text?: string;
19
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Presentation Layer Exports (60 lines)
2
+ * Presentation Layer Exports
3
3
  * Components, hooks, and contexts
4
4
  */
5
5
 
@@ -7,12 +7,18 @@
7
7
  export { MascotView } from './presentation/components/MascotView';
8
8
  export type { MascotViewProps } from './presentation/components/MascotView';
9
9
 
10
+ export { SimpleMascot } from './presentation/components/SimpleMascot';
11
+ export type { SimpleMascotProps } from './presentation/components/SimpleMascot';
12
+
10
13
  export { LottieMascot } from './presentation/components/LottieMascot';
11
14
  export type { LottieMascotProps } from './presentation/components/LottieMascot';
12
15
 
13
16
  export { SVGMascot } from './presentation/components/SVGMascot';
14
17
  export type { SVGMascotProps } from './presentation/components/SVGMascot';
15
18
 
19
+ // Presentation - Types
20
+ export type { MascotState, MascotSize, MascotTheme } from './presentation/components/types';
21
+
16
22
  // Presentation - Hooks
17
23
  export { useMascot } from './presentation/hooks/useMascot';
18
24
  export type {