@umituz/react-native-design-system 2.3.13 → 2.3.15

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 (106) hide show
  1. package/package.json +32 -13
  2. package/src/index.ts +116 -0
  3. package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +2 -2
  4. package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -1
  5. package/src/molecules/animation/core/AnimationCore.ts +29 -0
  6. package/src/molecules/animation/domain/entities/Animation.ts +81 -0
  7. package/src/molecules/animation/domain/entities/Fireworks.ts +44 -0
  8. package/src/molecules/animation/domain/entities/Theme.ts +76 -0
  9. package/src/molecules/animation/index.ts +146 -0
  10. package/src/molecules/animation/infrastructure/services/AnimationConfigService.ts +35 -0
  11. package/src/molecules/animation/infrastructure/services/SpringAnimationConfigService.ts +67 -0
  12. package/src/molecules/animation/infrastructure/services/TimingAnimationConfigService.ts +57 -0
  13. package/src/molecules/animation/infrastructure/services/__tests__/SpringAnimationConfigService.test.ts +114 -0
  14. package/src/molecules/animation/infrastructure/services/__tests__/TimingAnimationConfigService.test.ts +105 -0
  15. package/src/molecules/animation/presentation/components/Fireworks.tsx +126 -0
  16. package/src/molecules/animation/presentation/components/__tests__/Fireworks.test.tsx +189 -0
  17. package/src/molecules/animation/presentation/hooks/__tests__/useAnimation.integration.test.ts +216 -0
  18. package/src/molecules/animation/presentation/hooks/__tests__/useFireworks.test.ts +242 -0
  19. package/src/molecules/animation/presentation/hooks/__tests__/useGesture.test.ts +111 -0
  20. package/src/molecules/animation/presentation/hooks/__tests__/useSpringAnimation.test.ts +131 -0
  21. package/src/molecules/animation/presentation/hooks/__tests__/useTimingAnimation.test.ts +175 -0
  22. package/src/molecules/animation/presentation/hooks/__tests__/useTransformAnimation.test.ts +137 -0
  23. package/src/molecules/animation/presentation/hooks/useAnimation.ts +77 -0
  24. package/src/molecules/animation/presentation/hooks/useFireworks.ts +141 -0
  25. package/src/molecules/animation/presentation/hooks/useGesture.ts +61 -0
  26. package/src/molecules/animation/presentation/hooks/useGestureCreators.ts +163 -0
  27. package/src/molecules/animation/presentation/hooks/useGestureState.ts +53 -0
  28. package/src/molecules/animation/presentation/hooks/useIconAnimations.ts +119 -0
  29. package/src/molecules/animation/presentation/hooks/useModalAnimations.ts +124 -0
  30. package/src/molecules/animation/presentation/hooks/useReanimatedReady.ts +60 -0
  31. package/src/molecules/animation/presentation/hooks/useSpringAnimation.ts +69 -0
  32. package/src/molecules/animation/presentation/hooks/useTimingAnimation.ts +111 -0
  33. package/src/molecules/animation/presentation/hooks/useTransformAnimation.ts +57 -0
  34. package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +62 -0
  35. package/src/molecules/animation/presentation/providers/__tests__/AnimationThemeProvider.test.tsx +165 -0
  36. package/src/molecules/animation/types/global.d.ts +97 -0
  37. package/src/molecules/calendar/domain/entities/CalendarDay.entity.ts +115 -0
  38. package/src/molecules/calendar/domain/entities/CalendarEvent.entity.ts +202 -0
  39. package/src/molecules/calendar/domain/repositories/ICalendarRepository.ts +120 -0
  40. package/src/molecules/calendar/index.ts +98 -0
  41. package/src/molecules/calendar/infrastructure/services/CalendarEvents.ts +196 -0
  42. package/src/molecules/calendar/infrastructure/services/CalendarGeneration.ts +172 -0
  43. package/src/molecules/calendar/infrastructure/services/CalendarPermissions.ts +92 -0
  44. package/src/molecules/calendar/infrastructure/services/CalendarService.ts +161 -0
  45. package/src/molecules/calendar/infrastructure/services/CalendarSync.ts +205 -0
  46. package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +307 -0
  47. package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +128 -0
  48. package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +279 -0
  49. package/src/molecules/calendar/presentation/hooks/useCalendar.ts +356 -0
  50. package/src/molecules/celebration/domain/entities/CelebrationConfig.ts +17 -0
  51. package/src/molecules/celebration/domain/entities/FireworksConfig.ts +32 -0
  52. package/src/molecules/celebration/index.ts +93 -0
  53. package/src/molecules/celebration/infrastructure/services/FireworksConfigService.ts +49 -0
  54. package/src/molecules/celebration/presentation/components/CelebrationFireworksOverlay.tsx +33 -0
  55. package/src/molecules/celebration/presentation/components/CelebrationModal.tsx +78 -0
  56. package/src/molecules/celebration/presentation/components/CelebrationModalContent.tsx +90 -0
  57. package/src/molecules/celebration/presentation/hooks/useCelebrationModalAnimation.ts +49 -0
  58. package/src/molecules/celebration/presentation/hooks/useCelebrationState.ts +45 -0
  59. package/src/molecules/celebration/presentation/styles/CelebrationModalStyles.ts +65 -0
  60. package/src/molecules/countdown/components/Countdown.tsx +128 -0
  61. package/src/molecules/countdown/components/CountdownHeader.tsx +84 -0
  62. package/src/molecules/countdown/components/TimeUnit.tsx +73 -0
  63. package/src/molecules/countdown/hooks/useCountdown.ts +107 -0
  64. package/src/molecules/countdown/index.ts +25 -0
  65. package/src/molecules/countdown/types/CountdownTypes.ts +31 -0
  66. package/src/molecules/countdown/utils/TimeCalculator.ts +46 -0
  67. package/src/molecules/emoji/domain/entities/Emoji.ts +129 -0
  68. package/src/molecules/emoji/index.ts +177 -0
  69. package/src/molecules/emoji/presentation/components/EmojiPicker.tsx +102 -0
  70. package/src/molecules/emoji/presentation/hooks/useEmojiPicker.ts +171 -0
  71. package/src/molecules/index.ts +24 -0
  72. package/src/molecules/long-press-menu/domain/entities/MenuAction.ts +37 -0
  73. package/src/molecules/long-press-menu/index.ts +16 -0
  74. package/src/molecules/navigation/StackNavigator.tsx +75 -0
  75. package/src/molecules/navigation/TabsNavigator.tsx +94 -0
  76. package/src/molecules/navigation/components/FabButton.tsx +45 -0
  77. package/src/molecules/navigation/components/TabLabel.tsx +47 -0
  78. package/src/molecules/navigation/createStackNavigator.ts +20 -0
  79. package/src/molecules/navigation/createTabNavigator.ts +20 -0
  80. package/src/molecules/navigation/hooks/useTabBarStyles.ts +54 -0
  81. package/src/molecules/navigation/index.ts +37 -0
  82. package/src/molecules/navigation/types.ts +118 -0
  83. package/src/molecules/navigation/utils/AppNavigation.ts +101 -0
  84. package/src/molecules/navigation/utils/IconRenderer.ts +50 -0
  85. package/src/molecules/navigation/utils/LabelProcessor.ts +70 -0
  86. package/src/molecules/navigation/utils/NavigationCleanup.ts +62 -0
  87. package/src/molecules/navigation/utils/NavigationTheme.ts +21 -0
  88. package/src/molecules/navigation/utils/NavigationValidator.ts +61 -0
  89. package/src/molecules/navigation/utils/ScreenFactory.ts +115 -0
  90. package/src/molecules/navigation/utils/__tests__/IconRenderer.getIconName.test.ts +109 -0
  91. package/src/molecules/navigation/utils/__tests__/IconRenderer.renderIcon.test.ts +116 -0
  92. package/src/molecules/navigation/utils/__tests__/LabelProcessor.processLabel.test.ts +116 -0
  93. package/src/molecules/navigation/utils/__tests__/LabelProcessor.processTitle.test.ts +59 -0
  94. package/src/molecules/navigation/utils/__tests__/NavigationCleanup.test.ts +271 -0
  95. package/src/molecules/navigation/utils/__tests__/NavigationValidator.test.ts +252 -0
  96. package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +194 -0
  97. package/src/molecules/swipe-actions/index.ts +6 -0
  98. package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +131 -0
  99. package/src/theme/hooks/useResponsiveDesignTokens.ts +1 -1
  100. package/src/utilities/clipboard/ClipboardUtils.ts +71 -0
  101. package/src/utilities/clipboard/index.ts +5 -0
  102. package/src/utilities/index.ts +6 -0
  103. package/src/utilities/sharing/domain/entities/Share.ts +210 -0
  104. package/src/utilities/sharing/index.ts +205 -0
  105. package/src/utilities/sharing/infrastructure/services/SharingService.ts +165 -0
  106. package/src/utilities/sharing/presentation/hooks/useSharing.ts +154 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * useTimingAnimation Hook Tests
3
+ *
4
+ * Unit tests for timing-based animations (fade, slide).
5
+ */
6
+
7
+ import { renderHook, act } from '@testing-library/react';
8
+ import { useTimingAnimation } from '../useTimingAnimation';
9
+ import { AnimationPreset, ANIMATION_CONSTANTS } from '../../../domain/entities/Animation';
10
+
11
+ // Mock react-native-reanimated
12
+ jest.mock('react-native-reanimated', () => ({
13
+ useSharedValue: jest.fn((initialValue) => ({
14
+ value: initialValue,
15
+ })),
16
+ useAnimatedStyle: jest.fn((styleFactory) => styleFactory()),
17
+ withTiming: jest.fn((toValue, config) => ({ toValue, config })),
18
+ Easing: {
19
+ ease: jest.fn(),
20
+ out: jest.fn((easing) => easing),
21
+ bezier: jest.fn(() => jest.fn()),
22
+ },
23
+ }));
24
+
25
+ // Mock AnimationConfigService
26
+ jest.mock('../../../infrastructure/services/TimingAnimationConfigService', () => ({
27
+ TimingAnimationConfigService: {
28
+ getTimingConfig: jest.fn((preset) => ({
29
+ duration: ANIMATION_CONSTANTS.DURATION.NORMAL,
30
+ })),
31
+ },
32
+ }));
33
+
34
+ describe('useTimingAnimation', () => {
35
+ beforeEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+
39
+ it('should initialize with default values', () => {
40
+ const { result } = renderHook(() => useTimingAnimation());
41
+
42
+ expect(result.current.opacity.value).toBe(1);
43
+ expect(result.current.translateY.value).toBe(0);
44
+ expect(result.current.translateX.value).toBe(0);
45
+ });
46
+
47
+ it('should provide animation functions', () => {
48
+ const { result } = renderHook(() => useTimingAnimation());
49
+
50
+ expect(typeof result.current.fadeIn).toBe('function');
51
+ expect(typeof result.current.fadeOut).toBe('function');
52
+ expect(typeof result.current.slideInUp).toBe('function');
53
+ expect(typeof result.current.slideInDown).toBe('function');
54
+ expect(typeof result.current.slideInLeft).toBe('function');
55
+ expect(typeof result.current.slideInRight).toBe('function');
56
+ });
57
+
58
+ describe('fadeIn', () => {
59
+ it('should animate opacity to 1 with default config', () => {
60
+ const { result } = renderHook(() => useTimingAnimation());
61
+ const { withTiming } = require('react-native-reanimated');
62
+
63
+ act(() => {
64
+ result.current.fadeIn();
65
+ });
66
+
67
+ expect(withTiming).toHaveBeenCalledWith(1, {
68
+ duration: ANIMATION_CONSTANTS.DURATION.NORMAL,
69
+ easing: expect.any(Function),
70
+ });
71
+ });
72
+
73
+ it('should use custom config when provided', () => {
74
+ const { result } = renderHook(() => useTimingAnimation());
75
+ const { withTiming } = require('react-native-reanimated');
76
+ const customConfig = { duration: 500 };
77
+
78
+ act(() => {
79
+ result.current.fadeIn(customConfig);
80
+ });
81
+
82
+ expect(withTiming).toHaveBeenCalledWith(1, customConfig);
83
+ });
84
+ });
85
+
86
+ describe('fadeOut', () => {
87
+ it('should animate opacity to 0 with default config', () => {
88
+ const { result } = renderHook(() => useTimingAnimation());
89
+ const { withTiming } = require('react-native-reanimated');
90
+
91
+ act(() => {
92
+ result.current.fadeOut();
93
+ });
94
+
95
+ expect(withTiming).toHaveBeenCalledWith(0, {
96
+ duration: ANIMATION_CONSTANTS.DURATION.NORMAL,
97
+ easing: expect.any(Function),
98
+ });
99
+ });
100
+ });
101
+
102
+ describe('slideInUp', () => {
103
+ it('should slide from bottom to top with default distance', () => {
104
+ const { result } = renderHook(() => useTimingAnimation());
105
+ const { withTiming } = require('react-native-reanimated');
106
+
107
+ act(() => {
108
+ result.current.slideInUp();
109
+ });
110
+
111
+ expect(result.current.translateY.value).toBe(0);
112
+ expect(withTiming).toHaveBeenCalledWith(0, {
113
+ duration: ANIMATION_CONSTANTS.DURATION.NORMAL,
114
+ easing: expect.any(Function),
115
+ });
116
+ });
117
+
118
+ it('should use custom distance', () => {
119
+ const { result } = renderHook(() => useTimingAnimation());
120
+ const customDistance = 150;
121
+
122
+ act(() => {
123
+ result.current.slideInUp(customDistance);
124
+ });
125
+
126
+ expect(result.current.translateY.value).toBe(0);
127
+ });
128
+ });
129
+
130
+ describe('slideInDown', () => {
131
+ it('should slide from top to bottom', () => {
132
+ const { result } = renderHook(() => useTimingAnimation());
133
+
134
+ act(() => {
135
+ result.current.slideInDown();
136
+ });
137
+
138
+ expect(result.current.translateY.value).toBe(0);
139
+ });
140
+ });
141
+
142
+ describe('slideInLeft', () => {
143
+ it('should slide from left to right', () => {
144
+ const { result } = renderHook(() => useTimingAnimation());
145
+
146
+ act(() => {
147
+ result.current.slideInLeft();
148
+ });
149
+
150
+ expect(result.current.translateX.value).toBe(0);
151
+ });
152
+ });
153
+
154
+ describe('slideInRight', () => {
155
+ it('should slide from right to left', () => {
156
+ const { result } = renderHook(() => useTimingAnimation());
157
+
158
+ act(() => {
159
+ result.current.slideInRight();
160
+ });
161
+
162
+ expect(result.current.translateX.value).toBe(0);
163
+ });
164
+ });
165
+
166
+ describe('shared values', () => {
167
+ it('should expose shared values for custom animations', () => {
168
+ const { result } = renderHook(() => useTimingAnimation());
169
+
170
+ expect(result.current.opacity).toBeDefined();
171
+ expect(result.current.translateY).toBeDefined();
172
+ expect(result.current.translateX).toBeDefined();
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * useTransformAnimation Hook Tests
3
+ *
4
+ * Unit tests for transform-based animations (spin, pulse, shake).
5
+ */
6
+
7
+ import { renderHook, act } from '@testing-library/react';
8
+ import { useTransformAnimation } from '../useTransformAnimation';
9
+
10
+ // Mock react-native-reanimated
11
+ jest.mock('react-native-reanimated', () => ({
12
+ useSharedValue: jest.fn((initialValue) => ({
13
+ value: initialValue,
14
+ })),
15
+ useAnimatedStyle: jest.fn((styleFactory) => styleFactory()),
16
+ withTiming: jest.fn((toValue, config) => ({ toValue, config })),
17
+ withSequence: jest.fn((...animations) => animations),
18
+ withRepeat: jest.fn((animation, count, reverse) => ({ animation, count, reverse })),
19
+ Easing: {
20
+ linear: jest.fn(),
21
+ },
22
+ }));
23
+
24
+ describe('useTransformAnimation', () => {
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ });
28
+
29
+ it('should initialize with default values', () => {
30
+ const { result } = renderHook(() => useTransformAnimation());
31
+
32
+ expect(result.current.translateX.value).toBe(0);
33
+ expect(result.current.scale.value).toBe(1);
34
+ expect(result.current.rotate.value).toBe(0);
35
+ });
36
+
37
+ it('should provide animation functions', () => {
38
+ const { result } = renderHook(() => useTransformAnimation());
39
+
40
+ expect(typeof result.current.shake).toBe('function');
41
+ expect(typeof result.current.pulse).toBe('function');
42
+ expect(typeof result.current.spin).toBe('function');
43
+ });
44
+
45
+ describe('shake', () => {
46
+ it('should create shake animation sequence', () => {
47
+ const { result } = renderHook(() => useTransformAnimation());
48
+ const { withTiming, withSequence, withRepeat } = require('react-native-reanimated');
49
+
50
+ act(() => {
51
+ result.current.shake();
52
+ });
53
+
54
+ expect(withSequence).toHaveBeenCalledWith(
55
+ withTiming(-10, { duration: 50 }),
56
+ withRepeat(withTiming(10, { duration: 50 }), 4, true),
57
+ withTiming(0, { duration: 50 })
58
+ );
59
+ });
60
+ });
61
+
62
+ describe('pulse', () => {
63
+ it('should create pulse animation with default repeat count', () => {
64
+ const { result } = renderHook(() => useTransformAnimation());
65
+ const { withTiming, withSequence, withRepeat } = require('react-native-reanimated');
66
+
67
+ act(() => {
68
+ result.current.pulse();
69
+ });
70
+
71
+ expect(withRepeat).toHaveBeenCalledWith(
72
+ withSequence(withTiming(1.1, { duration: 500 }), withTiming(1, { duration: 500 })),
73
+ -1,
74
+ false
75
+ );
76
+ });
77
+
78
+ it('should use custom repeat count', () => {
79
+ const { result } = renderHook(() => useTransformAnimation());
80
+ const { withTiming, withSequence, withRepeat } = require('react-native-reanimated');
81
+ const customRepeatCount = 3;
82
+
83
+ act(() => {
84
+ result.current.pulse(customRepeatCount);
85
+ });
86
+
87
+ expect(withRepeat).toHaveBeenCalledWith(
88
+ expect.any(Object),
89
+ customRepeatCount,
90
+ false
91
+ );
92
+ });
93
+ });
94
+
95
+ describe('spin', () => {
96
+ it('should create spin animation with default repeat count', () => {
97
+ const { result } = renderHook(() => useTransformAnimation());
98
+ const { withTiming, withRepeat, Easing } = require('react-native-reanimated');
99
+
100
+ act(() => {
101
+ result.current.spin();
102
+ });
103
+
104
+ expect(withRepeat).toHaveBeenCalledWith(
105
+ withTiming(360, { duration: 1000, easing: Easing.linear }),
106
+ -1,
107
+ false
108
+ );
109
+ });
110
+
111
+ it('should use custom repeat count', () => {
112
+ const { result } = renderHook(() => useTransformAnimation());
113
+ const { withTiming, withRepeat } = require('react-native-reanimated');
114
+ const customRepeatCount = 2;
115
+
116
+ act(() => {
117
+ result.current.spin(customRepeatCount);
118
+ });
119
+
120
+ expect(withRepeat).toHaveBeenCalledWith(
121
+ expect.any(Object),
122
+ customRepeatCount,
123
+ false
124
+ );
125
+ });
126
+ });
127
+
128
+ describe('shared values', () => {
129
+ it('should expose shared values for custom animations', () => {
130
+ const { result } = renderHook(() => useTransformAnimation());
131
+
132
+ expect(result.current.translateX).toBeDefined();
133
+ expect(result.current.scale).toBeDefined();
134
+ expect(result.current.rotate).toBeDefined();
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * useAnimation Hook
3
+ *
4
+ * Orchestrator hook that combines all animation types.
5
+ * Single Responsibility: Compose and expose animation hooks.
6
+ */
7
+
8
+ import { useAnimatedStyle } from 'react-native-reanimated';
9
+ import { useTimingAnimation } from './useTimingAnimation';
10
+ import { useSpringAnimation } from './useSpringAnimation';
11
+ import { useTransformAnimation } from './useTransformAnimation';
12
+
13
+ /**
14
+ * Hook for declarative animations
15
+ *
16
+ * @example
17
+ * const { fadeIn, fadeOut, animatedStyle } = useAnimation();
18
+ *
19
+ * // Trigger animations
20
+ * fadeIn();
21
+ * fadeOut();
22
+ *
23
+ * // Apply to component
24
+ * <Animated.View style={animatedStyle}>...</Animated.View>
25
+ */
26
+ export const useAnimation = () => {
27
+ const timing = useTimingAnimation();
28
+ const spring = useSpringAnimation();
29
+ const transform = useTransformAnimation();
30
+
31
+ // Combine all shared values for animated style
32
+ const animatedStyle = useAnimatedStyle(() => {
33
+ // Use transform values if they're non-zero, otherwise use timing/spring values
34
+ const translateXValue = transform.translateX.value !== 0
35
+ ? transform.translateX.value
36
+ : timing.translateX.value;
37
+ const scaleValue = transform.scale.value !== 1
38
+ ? transform.scale.value
39
+ : spring.scale.value;
40
+
41
+ return {
42
+ opacity: timing.opacity.value,
43
+ transform: [
44
+ { translateY: timing.translateY.value },
45
+ { translateX: translateXValue },
46
+ { scale: scaleValue },
47
+ { rotate: `${transform.rotate.value}deg` },
48
+ ],
49
+ };
50
+ });
51
+
52
+ return {
53
+ // Timing animations
54
+ fadeIn: timing.fadeIn,
55
+ fadeOut: timing.fadeOut,
56
+ slideInUp: timing.slideInUp,
57
+ slideInDown: timing.slideInDown,
58
+ slideInLeft: timing.slideInLeft,
59
+ slideInRight: timing.slideInRight,
60
+ // Spring animations
61
+ scaleIn: spring.scaleIn,
62
+ scaleOut: spring.scaleOut,
63
+ bounce: spring.bounce,
64
+ // Transform animations
65
+ shake: transform.shake,
66
+ pulse: transform.pulse,
67
+ spin: transform.spin,
68
+ // Shared values (for custom animations)
69
+ opacity: timing.opacity,
70
+ translateY: timing.translateY,
71
+ translateX: timing.translateX,
72
+ scale: spring.scale,
73
+ rotate: transform.rotate,
74
+ // Animated style
75
+ animatedStyle,
76
+ };
77
+ };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * useFireworks Hook
3
+ *
4
+ * Hook for managing fireworks particle system.
5
+ * Single Responsibility: Manage fireworks state and lifecycle.
6
+ */
7
+
8
+ import { useState, useCallback, useRef, useEffect } from 'react';
9
+ import type { ParticleConfig, FireworksConfig } from '../../domain/entities/Fireworks';
10
+ import { FIREWORKS_CONSTANTS } from '../../domain/entities/Fireworks';
11
+
12
+ /**
13
+ * Hook for fireworks animation
14
+ *
15
+ * @example
16
+ * const { particles, trigger, isActive } = useFireworks();
17
+ *
18
+ * // Trigger fireworks
19
+ * trigger();
20
+ *
21
+ * // Render particles
22
+ * {particles.map((particle, index) => (
23
+ * <Particle key={index} {...particle} />
24
+ * ))}
25
+ */
26
+ export const useFireworks = (config: FireworksConfig) => {
27
+ const [particles, setParticles] = useState<ParticleConfig[]>([]);
28
+ const [isActive, setIsActive] = useState(false);
29
+ const animationFrameRef = useRef<number | null>(null);
30
+ const particlesRef = useRef<ParticleConfig[]>([]);
31
+
32
+ const {
33
+ particleCount = FIREWORKS_CONSTANTS.DEFAULT_PARTICLE_COUNT,
34
+ colors,
35
+ duration = FIREWORKS_CONSTANTS.DEFAULT_DURATION,
36
+ particleSize = FIREWORKS_CONSTANTS.DEFAULT_PARTICLE_SIZE,
37
+ spread = FIREWORKS_CONSTANTS.DEFAULT_SPREAD,
38
+ } = config;
39
+
40
+ if (!colors || colors.length === 0) {
41
+ if (__DEV__) {
42
+ console.warn('useFireworks: colors array is required and cannot be empty');
43
+ }
44
+ return { particles: [], trigger: () => {}, isActive: false };
45
+ }
46
+
47
+ const createParticle = useCallback((
48
+ x: number,
49
+ y: number,
50
+ color: string
51
+ ): ParticleConfig => {
52
+ const angle = Math.random() * Math.PI * 2;
53
+ const velocity = Math.random() * spread + 50;
54
+
55
+ return {
56
+ x,
57
+ y,
58
+ color,
59
+ size: particleSize + Math.random() * 2,
60
+ velocityX: Math.cos(angle) * velocity,
61
+ velocityY: Math.sin(angle) * velocity,
62
+ life: 1.0,
63
+ decay: FIREWORKS_CONSTANTS.DECAY_RATE + Math.random() * 0.01,
64
+ };
65
+ }, [particleSize, spread]);
66
+
67
+ const updateParticles = useCallback(() => {
68
+ const currentParticles = [...particlesRef.current];
69
+ const updated: ParticleConfig[] = [];
70
+
71
+ for (const particle of currentParticles) {
72
+ const newX = particle.x + particle.velocityX * 0.1;
73
+ const newY = particle.y + particle.velocityY * 0.1 + FIREWORKS_CONSTANTS.GRAVITY;
74
+ const newVelocityY = particle.velocityY + FIREWORKS_CONSTANTS.GRAVITY;
75
+ const newLife = particle.life - particle.decay;
76
+
77
+ if (newLife > 0) {
78
+ updated.push({
79
+ ...particle,
80
+ x: newX,
81
+ y: newY,
82
+ velocityY: newVelocityY,
83
+ life: newLife,
84
+ });
85
+ }
86
+ }
87
+
88
+ particlesRef.current = updated;
89
+ setParticles(updated);
90
+
91
+ if (updated.length > 0 && isActive) {
92
+ animationFrameRef.current = requestAnimationFrame(updateParticles);
93
+ } else {
94
+ setIsActive(false);
95
+ }
96
+ }, [isActive]);
97
+
98
+ const trigger = useCallback((x?: number, y?: number) => {
99
+ const centerX = x ?? 0;
100
+ const centerY = y ?? 0;
101
+
102
+ const newParticles: ParticleConfig[] = [];
103
+ for (let i = 0; i < particleCount; i++) {
104
+ const color = colors[Math.floor(Math.random() * colors.length)];
105
+ newParticles.push(createParticle(centerX, centerY, color));
106
+ }
107
+
108
+ // Create new array reference to avoid worklet issues
109
+ const particlesArray = [...newParticles];
110
+ particlesRef.current = particlesArray;
111
+ setParticles(particlesArray);
112
+ setIsActive(true);
113
+
114
+ if (animationFrameRef.current) {
115
+ cancelAnimationFrame(animationFrameRef.current);
116
+ }
117
+ animationFrameRef.current = requestAnimationFrame(updateParticles);
118
+
119
+ // Auto-stop after duration
120
+ setTimeout(() => {
121
+ setIsActive(false);
122
+ if (animationFrameRef.current) {
123
+ cancelAnimationFrame(animationFrameRef.current);
124
+ }
125
+ }, duration);
126
+ }, [particleCount, colors, duration, createParticle, updateParticles]);
127
+
128
+ useEffect(() => {
129
+ return () => {
130
+ if (animationFrameRef.current) {
131
+ cancelAnimationFrame(animationFrameRef.current);
132
+ }
133
+ };
134
+ }, []);
135
+
136
+ return {
137
+ particles,
138
+ trigger,
139
+ isActive,
140
+ };
141
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * useGesture Hook
3
+ *
4
+ * Orchestrator hook that combines gesture creators and state.
5
+ * Single Responsibility: Compose and expose gesture functionality.
6
+ */
7
+
8
+ import { GestureDetector } from 'react-native-gesture-handler';
9
+ import { useGestureState } from './useGestureState';
10
+ import { useGestureCreators } from './useGestureCreators';
11
+ import type {
12
+ TapGestureOptions,
13
+ PanGestureOptions,
14
+ PinchGestureOptions,
15
+ } from './useGestureCreators';
16
+
17
+ /**
18
+ * Hook for gesture handling
19
+ *
20
+ * @example
21
+ * const { createPanGesture, animatedStyle, GestureDetector } = useGesture();
22
+ *
23
+ * const panGesture = createPanGesture({
24
+ * onEnd: (x, y) => console.log('Dragged to:', x, y),
25
+ * });
26
+ *
27
+ * return (
28
+ * <GestureDetector gesture={panGesture}>
29
+ * <Animated.View style={animatedStyle}>...</Animated.View>
30
+ * </GestureDetector>
31
+ * );
32
+ */
33
+ export const useGesture = () => {
34
+ const state = useGestureState();
35
+ const creators = useGestureCreators(state);
36
+
37
+ return {
38
+ // Gesture creators
39
+ createTapGesture: creators.createTapGesture,
40
+ createPanGesture: creators.createPanGesture,
41
+ createPinchGesture: creators.createPinchGesture,
42
+ createLongPressGesture: creators.createLongPressGesture,
43
+ // Shared values (for custom gestures)
44
+ translateX: state.translateX,
45
+ translateY: state.translateY,
46
+ scale: state.scale,
47
+ // Utilities
48
+ reset: state.reset,
49
+ // Animated style
50
+ animatedStyle: state.animatedStyle,
51
+ // Re-export GestureDetector for convenience
52
+ GestureDetector: GestureDetector as typeof GestureDetector,
53
+ };
54
+ };
55
+
56
+ // Re-export types for convenience
57
+ export type {
58
+ TapGestureOptions,
59
+ PanGestureOptions,
60
+ PinchGestureOptions,
61
+ } from './useGestureCreators';