react-native-puff-pop 1.0.6 â 1.0.7
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/README.md +193 -0
- package/lib/module/index.js +109 -47
- package/lib/typescript/src/index.d.ts +115 -2
- package/package.json +1 -1
- package/src/index.tsx +285 -44
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ Works with both **React Native CLI** and **Expo** projects - no native dependenc
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
18
|
- ðŽ **11 Animation Effects**: scale, rotate, fade, slideUp, slideDown, slideLeft, slideRight, bounce, flip, zoom, rotateScale
|
|
19
|
+
- ðŊ **Exit Animations**: Different effects for enter and exit animations
|
|
20
|
+
- ð **Reverse Mode**: Reverse animation direction with a single prop
|
|
21
|
+
- ðïļ **Custom Initial Values**: Fine-tune starting opacity, scale, rotation, and position
|
|
22
|
+
- ð **Animation Intensity**: Control how dramatic your animations are (0-1)
|
|
19
23
|
- ðĶī **Skeleton Mode**: Reserve space before animation or expand from zero height
|
|
20
24
|
- ⥠**Native Driver Support**: Smooth 60fps animations
|
|
21
25
|
- ðŊ **Easy to Use**: Just wrap your components with `<PuffPop>`
|
|
@@ -181,6 +185,173 @@ function App() {
|
|
|
181
185
|
</PuffPop>
|
|
182
186
|
```
|
|
183
187
|
|
|
188
|
+
### Exit Animation
|
|
189
|
+
|
|
190
|
+
Use different effects for enter and exit animations:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// Scale in, fade out
|
|
194
|
+
<PuffPop
|
|
195
|
+
effect="scale"
|
|
196
|
+
exitEffect="fade"
|
|
197
|
+
visible={isVisible}
|
|
198
|
+
>
|
|
199
|
+
<Modal />
|
|
200
|
+
</PuffPop>
|
|
201
|
+
|
|
202
|
+
// Slide up to enter, slide down to exit
|
|
203
|
+
<PuffPop
|
|
204
|
+
effect="slideUp"
|
|
205
|
+
exitEffect="slideDown"
|
|
206
|
+
exitDuration={200}
|
|
207
|
+
visible={isVisible}
|
|
208
|
+
>
|
|
209
|
+
<Toast />
|
|
210
|
+
</PuffPop>
|
|
211
|
+
|
|
212
|
+
// Different timing for enter and exit
|
|
213
|
+
<PuffPop
|
|
214
|
+
effect="zoom"
|
|
215
|
+
duration={400}
|
|
216
|
+
easing="spring"
|
|
217
|
+
exitEffect="fade"
|
|
218
|
+
exitDuration={150}
|
|
219
|
+
exitEasing="easeIn"
|
|
220
|
+
exitDelay={50}
|
|
221
|
+
visible={isVisible}
|
|
222
|
+
>
|
|
223
|
+
<Notification />
|
|
224
|
+
</PuffPop>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
With `PuffPopGroup`:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<PuffPopGroup
|
|
231
|
+
effect="slideUp"
|
|
232
|
+
exitEffect="fade"
|
|
233
|
+
exitDuration={150}
|
|
234
|
+
staggerDelay={50}
|
|
235
|
+
visible={isVisible}
|
|
236
|
+
>
|
|
237
|
+
<ListItem />
|
|
238
|
+
<ListItem />
|
|
239
|
+
<ListItem />
|
|
240
|
+
</PuffPopGroup>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Custom Initial Values
|
|
244
|
+
|
|
245
|
+
Fine-tune the starting values of your animations:
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
// Start from 50% opacity instead of 0
|
|
249
|
+
<PuffPop initialOpacity={0.5}>
|
|
250
|
+
<Card />
|
|
251
|
+
</PuffPop>
|
|
252
|
+
|
|
253
|
+
// Start from a larger scale
|
|
254
|
+
<PuffPop effect="scale" initialScale={0.5}>
|
|
255
|
+
<Avatar />
|
|
256
|
+
</PuffPop>
|
|
257
|
+
|
|
258
|
+
// Custom slide distance
|
|
259
|
+
<PuffPop effect="slideUp" initialTranslateY={200}>
|
|
260
|
+
<Modal />
|
|
261
|
+
</PuffPop>
|
|
262
|
+
|
|
263
|
+
// Combine multiple custom values
|
|
264
|
+
<PuffPop
|
|
265
|
+
effect="rotateScale"
|
|
266
|
+
initialOpacity={0.3}
|
|
267
|
+
initialScale={0.2}
|
|
268
|
+
initialRotate={-90}
|
|
269
|
+
>
|
|
270
|
+
<Icon />
|
|
271
|
+
</PuffPop>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Reverse Mode
|
|
275
|
+
|
|
276
|
+
Reverse the animation direction:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// slideUp now slides from top instead of bottom
|
|
280
|
+
<PuffPop effect="slideUp" reverse>
|
|
281
|
+
<Toast />
|
|
282
|
+
</PuffPop>
|
|
283
|
+
|
|
284
|
+
// slideLeft now slides from left instead of right
|
|
285
|
+
<PuffPop effect="slideLeft" reverse>
|
|
286
|
+
<Drawer />
|
|
287
|
+
</PuffPop>
|
|
288
|
+
|
|
289
|
+
// rotate spins clockwise instead of counter-clockwise
|
|
290
|
+
<PuffPop effect="rotate" reverse>
|
|
291
|
+
<Spinner />
|
|
292
|
+
</PuffPop>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
With `PuffPopGroup`:
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
<PuffPopGroup effect="slideRight" reverse staggerDelay={50}>
|
|
299
|
+
<MenuItem />
|
|
300
|
+
<MenuItem />
|
|
301
|
+
<MenuItem />
|
|
302
|
+
</PuffPopGroup>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Animation Intensity
|
|
306
|
+
|
|
307
|
+
Control how dramatic your animations are with the `intensity` prop (0-1):
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
// Full animation (default)
|
|
311
|
+
<PuffPop effect="slideUp" intensity={1}>
|
|
312
|
+
<Card />
|
|
313
|
+
</PuffPop>
|
|
314
|
+
|
|
315
|
+
// Half the movement distance (25px instead of 50px)
|
|
316
|
+
<PuffPop effect="slideUp" intensity={0.5}>
|
|
317
|
+
<Card />
|
|
318
|
+
</PuffPop>
|
|
319
|
+
|
|
320
|
+
// Subtle animation (10px slide)
|
|
321
|
+
<PuffPop effect="slideUp" intensity={0.2}>
|
|
322
|
+
<Tooltip />
|
|
323
|
+
</PuffPop>
|
|
324
|
+
|
|
325
|
+
// No movement, just fade (intensity 0)
|
|
326
|
+
<PuffPop effect="slideUp" intensity={0}>
|
|
327
|
+
<Content />
|
|
328
|
+
</PuffPop>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Works with all effects:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// Smaller scale range (starts at 0.5 instead of 0)
|
|
335
|
+
<PuffPop effect="scale" intensity={0.5}>
|
|
336
|
+
<Avatar />
|
|
337
|
+
</PuffPop>
|
|
338
|
+
|
|
339
|
+
// Less rotation (180deg instead of 360deg)
|
|
340
|
+
<PuffPop effect="rotate" intensity={0.5}>
|
|
341
|
+
<Icon />
|
|
342
|
+
</PuffPop>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
With `PuffPopGroup`:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
<PuffPopGroup effect="slideLeft" intensity={0.3} staggerDelay={50}>
|
|
349
|
+
<ListItem />
|
|
350
|
+
<ListItem />
|
|
351
|
+
<ListItem />
|
|
352
|
+
</PuffPopGroup>
|
|
353
|
+
```
|
|
354
|
+
|
|
184
355
|
## Props
|
|
185
356
|
|
|
186
357
|
| Prop | Type | Default | Description |
|
|
@@ -200,6 +371,17 @@ function App() {
|
|
|
200
371
|
| `loopDelay` | `number` | `0` | Delay between loop iterations in ms |
|
|
201
372
|
| `respectReduceMotion` | `boolean` | `true` | Respect system reduce motion setting |
|
|
202
373
|
| `testID` | `string` | - | Test ID for testing purposes |
|
|
374
|
+
| `exitEffect` | `PuffPopEffect` | - | Animation effect for exit (defaults to enter effect) |
|
|
375
|
+
| `exitDuration` | `number` | - | Duration for exit animation (defaults to duration) |
|
|
376
|
+
| `exitEasing` | `PuffPopEasing` | - | Easing for exit animation (defaults to easing) |
|
|
377
|
+
| `exitDelay` | `number` | `0` | Delay before exit animation starts in ms |
|
|
378
|
+
| `initialOpacity` | `number` | - | Custom initial opacity (0-1) |
|
|
379
|
+
| `initialScale` | `number` | - | Custom initial scale value |
|
|
380
|
+
| `initialRotate` | `number` | - | Custom initial rotation in degrees |
|
|
381
|
+
| `initialTranslateX` | `number` | - | Custom initial X translation in pixels |
|
|
382
|
+
| `initialTranslateY` | `number` | - | Custom initial Y translation in pixels |
|
|
383
|
+
| `reverse` | `boolean` | `false` | Reverse animation direction |
|
|
384
|
+
| `intensity` | `number` | `1` | Animation intensity multiplier (0-1) |
|
|
203
385
|
|
|
204
386
|
### PuffPopGroup Props
|
|
205
387
|
|
|
@@ -222,6 +404,17 @@ function App() {
|
|
|
222
404
|
| `gap` | `number` | - | Gap between children |
|
|
223
405
|
| `respectReduceMotion` | `boolean` | `true` | Respect system reduce motion setting |
|
|
224
406
|
| `testID` | `string` | - | Test ID for testing purposes |
|
|
407
|
+
| `exitEffect` | `PuffPopEffect` | - | Exit animation effect for all children |
|
|
408
|
+
| `exitDuration` | `number` | - | Exit duration for all children |
|
|
409
|
+
| `exitEasing` | `PuffPopEasing` | - | Exit easing for all children |
|
|
410
|
+
| `exitDelay` | `number` | `0` | Exit delay for all children |
|
|
411
|
+
| `initialOpacity` | `number` | - | Custom initial opacity for all children |
|
|
412
|
+
| `initialScale` | `number` | - | Custom initial scale for all children |
|
|
413
|
+
| `initialRotate` | `number` | - | Custom initial rotation for all children |
|
|
414
|
+
| `initialTranslateX` | `number` | - | Custom initial X translation for all children |
|
|
415
|
+
| `initialTranslateY` | `number` | - | Custom initial Y translation for all children |
|
|
416
|
+
| `reverse` | `boolean` | `false` | Reverse animation direction for all children |
|
|
417
|
+
| `intensity` | `number` | `1` | Animation intensity multiplier for all children |
|
|
225
418
|
|
|
226
419
|
### Animation Effects (`PuffPopEffect`)
|
|
227
420
|
|
package/lib/module/index.js
CHANGED
|
@@ -25,13 +25,47 @@ function getEasing(type) {
|
|
|
25
25
|
/**
|
|
26
26
|
* PuffPop - Animate children with beautiful entrance effects
|
|
27
27
|
*/
|
|
28
|
-
export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0, easing = 'easeOut', skeleton = true, visible = true, onAnimationComplete, onAnimationStart, style, animateOnMount = true, loop = false, loopDelay = 0, respectReduceMotion = true, testID,
|
|
28
|
+
export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0, easing = 'easeOut', skeleton = true, visible = true, onAnimationComplete, onAnimationStart, style, animateOnMount = true, loop = false, loopDelay = 0, respectReduceMotion = true, testID,
|
|
29
|
+
// Exit animation settings
|
|
30
|
+
exitEffect, exitDuration, exitEasing, exitDelay = 0,
|
|
31
|
+
// Custom initial values
|
|
32
|
+
initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY,
|
|
33
|
+
// Reverse mode
|
|
34
|
+
reverse = false,
|
|
35
|
+
// Animation intensity
|
|
36
|
+
intensity = 1, }) {
|
|
37
|
+
// Clamp intensity between 0 and 1
|
|
38
|
+
const clampedIntensity = Math.max(0, Math.min(1, intensity));
|
|
39
|
+
// Helper to get initial value with custom override, reverse, and intensity support
|
|
40
|
+
const getInitialOpacityValue = () => initialOpacity ?? 0;
|
|
41
|
+
const getInitialScaleValue = (eff) => {
|
|
42
|
+
if (initialScale !== undefined)
|
|
43
|
+
return initialScale;
|
|
44
|
+
const baseScale = getInitialScale(eff, reverse);
|
|
45
|
+
// Scale goes from baseScale to 1, so we interpolate: 1 - (1 - baseScale) * intensity
|
|
46
|
+
return 1 - (1 - baseScale) * clampedIntensity;
|
|
47
|
+
};
|
|
48
|
+
const getInitialRotateValue = (eff) => {
|
|
49
|
+
if (initialRotate !== undefined)
|
|
50
|
+
return initialRotate;
|
|
51
|
+
return getInitialRotate(eff, reverse) * clampedIntensity;
|
|
52
|
+
};
|
|
53
|
+
const getInitialTranslateXValue = (eff) => {
|
|
54
|
+
if (initialTranslateX !== undefined)
|
|
55
|
+
return initialTranslateX;
|
|
56
|
+
return getInitialTranslateX(eff, reverse) * clampedIntensity;
|
|
57
|
+
};
|
|
58
|
+
const getInitialTranslateYValue = (eff) => {
|
|
59
|
+
if (initialTranslateY !== undefined)
|
|
60
|
+
return initialTranslateY;
|
|
61
|
+
return getInitialTranslateY(eff, reverse) * clampedIntensity;
|
|
62
|
+
};
|
|
29
63
|
// Animation values
|
|
30
|
-
const opacity = useRef(new Animated.Value(animateOnMount ?
|
|
31
|
-
const scale = useRef(new Animated.Value(animateOnMount ?
|
|
32
|
-
const rotate = useRef(new Animated.Value(animateOnMount ?
|
|
33
|
-
const translateX = useRef(new Animated.Value(animateOnMount ?
|
|
34
|
-
const translateY = useRef(new Animated.Value(animateOnMount ?
|
|
64
|
+
const opacity = useRef(new Animated.Value(animateOnMount ? getInitialOpacityValue() : 1)).current;
|
|
65
|
+
const scale = useRef(new Animated.Value(animateOnMount ? getInitialScaleValue(effect) : 1)).current;
|
|
66
|
+
const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotateValue(effect) : 0)).current;
|
|
67
|
+
const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateXValue(effect) : 0)).current;
|
|
68
|
+
const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateYValue(effect) : 0)).current;
|
|
35
69
|
// For non-skeleton mode
|
|
36
70
|
const [measuredHeight, setMeasuredHeight] = useState(null);
|
|
37
71
|
const animatedHeight = useRef(new Animated.Value(0)).current;
|
|
@@ -55,15 +89,20 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
55
89
|
}, [respectReduceMotion]);
|
|
56
90
|
// Effective duration (0 if reduce motion is enabled)
|
|
57
91
|
const effectiveDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : duration;
|
|
92
|
+
const effectiveExitDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : (exitDuration ?? duration);
|
|
93
|
+
// Helper to get effect flags for any effect type
|
|
94
|
+
const getEffectFlags = useCallback((eff) => ({
|
|
95
|
+
hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(eff),
|
|
96
|
+
hasRotate: ['rotate', 'rotateScale'].includes(eff),
|
|
97
|
+
hasFlip: eff === 'flip',
|
|
98
|
+
hasTranslateX: ['slideLeft', 'slideRight'].includes(eff),
|
|
99
|
+
hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(eff),
|
|
100
|
+
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(eff),
|
|
101
|
+
}), []);
|
|
58
102
|
// Memoize effect type checks to avoid repeated includes() calls
|
|
59
|
-
const effectFlags = useMemo(() => (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
hasFlip: effect === 'flip',
|
|
63
|
-
hasTranslateX: ['slideLeft', 'slideRight'].includes(effect),
|
|
64
|
-
hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(effect),
|
|
65
|
-
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
|
|
66
|
-
}), [effect]);
|
|
103
|
+
const effectFlags = useMemo(() => getEffectFlags(effect), [effect, getEffectFlags]);
|
|
104
|
+
// Exit effect flags (use exitEffect if specified, otherwise same as enter effect)
|
|
105
|
+
const exitEffectFlags = useMemo(() => exitEffect ? getEffectFlags(exitEffect) : effectFlags, [exitEffect, getEffectFlags, effectFlags]);
|
|
67
106
|
// Memoize interpolations to avoid recreating on every render
|
|
68
107
|
const rotateInterpolation = useMemo(() => rotate.interpolate({
|
|
69
108
|
inputRange: [-360, 0, 360],
|
|
@@ -86,12 +125,18 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
86
125
|
if (toVisible && onAnimationStart) {
|
|
87
126
|
onAnimationStart();
|
|
88
127
|
}
|
|
89
|
-
|
|
128
|
+
// Determine which effect settings to use based on direction
|
|
129
|
+
const currentEffect = toVisible ? effect : (exitEffect ?? effect);
|
|
130
|
+
const currentDuration = toVisible ? effectiveDuration : effectiveExitDuration;
|
|
131
|
+
const currentEasing = toVisible ? easing : (exitEasing ?? easing);
|
|
132
|
+
const currentDelay = toVisible ? delay : exitDelay;
|
|
133
|
+
const currentFlags = toVisible ? effectFlags : exitEffectFlags;
|
|
134
|
+
const easingFn = getEasing(currentEasing);
|
|
90
135
|
// When skeleton is false, we animate height which doesn't support native driver
|
|
91
136
|
// So we must use JS driver for all animations in that case
|
|
92
137
|
const useNative = skeleton;
|
|
93
138
|
const config = {
|
|
94
|
-
duration:
|
|
139
|
+
duration: currentDuration,
|
|
95
140
|
easing: easingFn,
|
|
96
141
|
useNativeDriver: useNative,
|
|
97
142
|
};
|
|
@@ -102,33 +147,33 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
102
147
|
...config,
|
|
103
148
|
}));
|
|
104
149
|
// Scale animation
|
|
105
|
-
if (
|
|
106
|
-
const targetScale = toVisible ? 1 :
|
|
150
|
+
if (currentFlags.hasScale) {
|
|
151
|
+
const targetScale = toVisible ? 1 : getInitialScaleValue(currentEffect);
|
|
107
152
|
animations.push(Animated.timing(scale, {
|
|
108
153
|
toValue: targetScale,
|
|
109
154
|
...config,
|
|
110
|
-
easing:
|
|
155
|
+
easing: currentEffect === 'bounce' ? Easing.bounce : easingFn,
|
|
111
156
|
}));
|
|
112
157
|
}
|
|
113
158
|
// Rotate animation
|
|
114
|
-
if (
|
|
115
|
-
const targetRotate = toVisible ? 0 :
|
|
159
|
+
if (currentFlags.hasRotate || currentFlags.hasFlip) {
|
|
160
|
+
const targetRotate = toVisible ? 0 : getInitialRotateValue(currentEffect);
|
|
116
161
|
animations.push(Animated.timing(rotate, {
|
|
117
162
|
toValue: targetRotate,
|
|
118
163
|
...config,
|
|
119
164
|
}));
|
|
120
165
|
}
|
|
121
166
|
// TranslateX animation
|
|
122
|
-
if (
|
|
123
|
-
const targetX = toVisible ? 0 :
|
|
167
|
+
if (currentFlags.hasTranslateX) {
|
|
168
|
+
const targetX = toVisible ? 0 : getInitialTranslateXValue(currentEffect);
|
|
124
169
|
animations.push(Animated.timing(translateX, {
|
|
125
170
|
toValue: targetX,
|
|
126
171
|
...config,
|
|
127
172
|
}));
|
|
128
173
|
}
|
|
129
174
|
// TranslateY animation
|
|
130
|
-
if (
|
|
131
|
-
const targetY = toVisible ? 0 :
|
|
175
|
+
if (currentFlags.hasTranslateY) {
|
|
176
|
+
const targetY = toVisible ? 0 : getInitialTranslateYValue(currentEffect);
|
|
132
177
|
animations.push(Animated.timing(translateY, {
|
|
133
178
|
toValue: targetY,
|
|
134
179
|
...config,
|
|
@@ -139,7 +184,7 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
139
184
|
const targetHeight = toVisible ? measuredHeight : 0;
|
|
140
185
|
animations.push(Animated.timing(animatedHeight, {
|
|
141
186
|
toValue: targetHeight,
|
|
142
|
-
duration:
|
|
187
|
+
duration: currentDuration,
|
|
143
188
|
easing: easingFn,
|
|
144
189
|
useNativeDriver: false,
|
|
145
190
|
}));
|
|
@@ -148,20 +193,20 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
148
193
|
const parallelAnimation = Animated.parallel(animations);
|
|
149
194
|
// Reset values function for looping
|
|
150
195
|
const resetValues = () => {
|
|
151
|
-
opacity.setValue(
|
|
152
|
-
scale.setValue(
|
|
153
|
-
rotate.setValue(
|
|
154
|
-
translateX.setValue(
|
|
155
|
-
translateY.setValue(
|
|
196
|
+
opacity.setValue(getInitialOpacityValue());
|
|
197
|
+
scale.setValue(getInitialScaleValue(effect));
|
|
198
|
+
rotate.setValue(getInitialRotateValue(effect));
|
|
199
|
+
translateX.setValue(getInitialTranslateXValue(effect));
|
|
200
|
+
translateY.setValue(getInitialTranslateYValue(effect));
|
|
156
201
|
if (!skeleton && measuredHeight !== null) {
|
|
157
202
|
animatedHeight.setValue(0);
|
|
158
203
|
}
|
|
159
204
|
};
|
|
160
205
|
// Build the animation sequence
|
|
161
206
|
let animation;
|
|
162
|
-
if (
|
|
207
|
+
if (currentDelay > 0) {
|
|
163
208
|
animation = Animated.sequence([
|
|
164
|
-
Animated.delay(
|
|
209
|
+
Animated.delay(currentDelay),
|
|
165
210
|
parallelAnimation,
|
|
166
211
|
]);
|
|
167
212
|
}
|
|
@@ -230,9 +275,14 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
230
275
|
}, [
|
|
231
276
|
delay,
|
|
232
277
|
effectiveDuration,
|
|
278
|
+
effectiveExitDuration,
|
|
233
279
|
easing,
|
|
234
280
|
effect,
|
|
235
281
|
effectFlags,
|
|
282
|
+
exitEffect,
|
|
283
|
+
exitEffectFlags,
|
|
284
|
+
exitEasing,
|
|
285
|
+
exitDelay,
|
|
236
286
|
measuredHeight,
|
|
237
287
|
onAnimationComplete,
|
|
238
288
|
onAnimationStart,
|
|
@@ -316,7 +366,8 @@ export function PuffPop({ children, effect = 'scale', duration = 400, delay = 0,
|
|
|
316
366
|
/**
|
|
317
367
|
* Get initial scale value based on effect
|
|
318
368
|
*/
|
|
319
|
-
function getInitialScale(effect) {
|
|
369
|
+
function getInitialScale(effect, _reverse = false) {
|
|
370
|
+
// Scale doesn't change with reverse (parameter kept for consistent API)
|
|
320
371
|
switch (effect) {
|
|
321
372
|
case 'scale':
|
|
322
373
|
case 'rotateScale':
|
|
@@ -334,14 +385,15 @@ function getInitialScale(effect) {
|
|
|
334
385
|
/**
|
|
335
386
|
* Get initial rotate value based on effect
|
|
336
387
|
*/
|
|
337
|
-
function getInitialRotate(effect) {
|
|
388
|
+
function getInitialRotate(effect, reverse = false) {
|
|
389
|
+
const multiplier = reverse ? -1 : 1;
|
|
338
390
|
switch (effect) {
|
|
339
391
|
case 'rotate':
|
|
340
|
-
return -360;
|
|
392
|
+
return -360 * multiplier;
|
|
341
393
|
case 'rotateScale':
|
|
342
|
-
return -180;
|
|
394
|
+
return -180 * multiplier;
|
|
343
395
|
case 'flip':
|
|
344
|
-
return -180;
|
|
396
|
+
return -180 * multiplier;
|
|
345
397
|
default:
|
|
346
398
|
return 0;
|
|
347
399
|
}
|
|
@@ -349,12 +401,13 @@ function getInitialRotate(effect) {
|
|
|
349
401
|
/**
|
|
350
402
|
* Get initial translateX value based on effect
|
|
351
403
|
*/
|
|
352
|
-
function getInitialTranslateX(effect) {
|
|
404
|
+
function getInitialTranslateX(effect, reverse = false) {
|
|
405
|
+
const multiplier = reverse ? -1 : 1;
|
|
353
406
|
switch (effect) {
|
|
354
407
|
case 'slideLeft':
|
|
355
|
-
return 100;
|
|
408
|
+
return 100 * multiplier;
|
|
356
409
|
case 'slideRight':
|
|
357
|
-
return -100;
|
|
410
|
+
return -100 * multiplier;
|
|
358
411
|
default:
|
|
359
412
|
return 0;
|
|
360
413
|
}
|
|
@@ -362,14 +415,15 @@ function getInitialTranslateX(effect) {
|
|
|
362
415
|
/**
|
|
363
416
|
* Get initial translateY value based on effect
|
|
364
417
|
*/
|
|
365
|
-
function getInitialTranslateY(effect) {
|
|
418
|
+
function getInitialTranslateY(effect, reverse = false) {
|
|
419
|
+
const multiplier = reverse ? -1 : 1;
|
|
366
420
|
switch (effect) {
|
|
367
421
|
case 'slideUp':
|
|
368
|
-
return 50;
|
|
422
|
+
return 50 * multiplier;
|
|
369
423
|
case 'slideDown':
|
|
370
|
-
return -50;
|
|
424
|
+
return -50 * multiplier;
|
|
371
425
|
case 'bounce':
|
|
372
|
-
return 30;
|
|
426
|
+
return 30 * multiplier;
|
|
373
427
|
default:
|
|
374
428
|
return 0;
|
|
375
429
|
}
|
|
@@ -398,7 +452,15 @@ const styles = StyleSheet.create({
|
|
|
398
452
|
* </PuffPopGroup>
|
|
399
453
|
* ```
|
|
400
454
|
*/
|
|
401
|
-
export function PuffPopGroup({ children, effect = 'scale', duration = 400, staggerDelay = 100, initialDelay = 0, easing = 'easeOut', skeleton = true, visible = true, animateOnMount = true, onAnimationComplete, onAnimationStart, style, respectReduceMotion = true, testID, staggerDirection = 'forward', horizontal = false, gap,
|
|
455
|
+
export function PuffPopGroup({ children, effect = 'scale', duration = 400, staggerDelay = 100, initialDelay = 0, easing = 'easeOut', skeleton = true, visible = true, animateOnMount = true, onAnimationComplete, onAnimationStart, style, respectReduceMotion = true, testID, staggerDirection = 'forward', horizontal = false, gap,
|
|
456
|
+
// Custom initial values
|
|
457
|
+
initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY,
|
|
458
|
+
// Reverse mode
|
|
459
|
+
reverse,
|
|
460
|
+
// Animation intensity
|
|
461
|
+
intensity,
|
|
462
|
+
// Exit animation settings
|
|
463
|
+
exitEffect, exitDuration, exitEasing, exitDelay, }) {
|
|
402
464
|
const childArray = Children.toArray(children);
|
|
403
465
|
const childCount = childArray.length;
|
|
404
466
|
const completedCount = useRef(0);
|
|
@@ -457,6 +519,6 @@ export function PuffPopGroup({ children, effect = 'scale', duration = 400, stagg
|
|
|
457
519
|
}
|
|
458
520
|
return baseStyle;
|
|
459
521
|
}, [horizontal, gap]);
|
|
460
|
-
return (_jsx(View, { style: [styles.groupContainer, containerStyle, style], testID: testID, children: childArray.map((child, index) => (_jsx(PuffPop, { effect: effect, duration: duration, delay: getChildDelay(index), easing: easing, skeleton: skeleton, visible: visible, animateOnMount: animateOnMount, onAnimationComplete: handleChildComplete, onAnimationStart: index === 0 ? handleChildStart : undefined, respectReduceMotion: respectReduceMotion, children: child }, index))) }));
|
|
522
|
+
return (_jsx(View, { style: [styles.groupContainer, containerStyle, style], testID: testID, children: childArray.map((child, index) => (_jsx(PuffPop, { effect: effect, duration: duration, delay: getChildDelay(index), easing: easing, skeleton: skeleton, visible: visible, animateOnMount: animateOnMount, onAnimationComplete: handleChildComplete, onAnimationStart: index === 0 ? handleChildStart : undefined, respectReduceMotion: respectReduceMotion, initialOpacity: initialOpacity, initialScale: initialScale, initialRotate: initialRotate, initialTranslateX: initialTranslateX, initialTranslateY: initialTranslateY, reverse: reverse, intensity: intensity, exitEffect: exitEffect, exitDuration: exitDuration, exitEasing: exitEasing, exitDelay: exitDelay, children: child }, index))) }));
|
|
461
523
|
}
|
|
462
524
|
export default PuffPop;
|
|
@@ -83,11 +83,73 @@ export interface PuffPopProps {
|
|
|
83
83
|
* Test ID for testing purposes
|
|
84
84
|
*/
|
|
85
85
|
testID?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Animation effect type when hiding (exit animation)
|
|
88
|
+
* If not specified, uses the reverse of the enter effect
|
|
89
|
+
*/
|
|
90
|
+
exitEffect?: PuffPopEffect;
|
|
91
|
+
/**
|
|
92
|
+
* Animation duration for exit animation in milliseconds
|
|
93
|
+
* If not specified, uses the same duration as enter animation
|
|
94
|
+
*/
|
|
95
|
+
exitDuration?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Easing function for exit animation
|
|
98
|
+
* If not specified, uses the same easing as enter animation
|
|
99
|
+
*/
|
|
100
|
+
exitEasing?: PuffPopEasing;
|
|
101
|
+
/**
|
|
102
|
+
* Delay before exit animation starts in milliseconds
|
|
103
|
+
* @default 0
|
|
104
|
+
*/
|
|
105
|
+
exitDelay?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Custom initial opacity value (0-1)
|
|
108
|
+
* Overrides the default initial opacity for the effect
|
|
109
|
+
*/
|
|
110
|
+
initialOpacity?: number;
|
|
111
|
+
/**
|
|
112
|
+
* Custom initial scale value
|
|
113
|
+
* Overrides the default initial scale for effects like 'scale', 'zoom', 'bounce'
|
|
114
|
+
*/
|
|
115
|
+
initialScale?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Custom initial rotation value in degrees
|
|
118
|
+
* Overrides the default initial rotation for effects like 'rotate', 'rotateScale'
|
|
119
|
+
*/
|
|
120
|
+
initialRotate?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Custom initial translateX value in pixels
|
|
123
|
+
* Overrides the default initial translateX for effects like 'slideLeft', 'slideRight'
|
|
124
|
+
*/
|
|
125
|
+
initialTranslateX?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Custom initial translateY value in pixels
|
|
128
|
+
* Overrides the default initial translateY for effects like 'slideUp', 'slideDown'
|
|
129
|
+
*/
|
|
130
|
+
initialTranslateY?: number;
|
|
131
|
+
/**
|
|
132
|
+
* If true, reverses the animation direction
|
|
133
|
+
* - slideUp becomes slide from top
|
|
134
|
+
* - slideLeft becomes slide from left
|
|
135
|
+
* - rotate spins clockwise instead of counter-clockwise
|
|
136
|
+
* @default false
|
|
137
|
+
*/
|
|
138
|
+
reverse?: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Animation intensity multiplier (0-1)
|
|
141
|
+
* Controls how far/much elements move during animation
|
|
142
|
+
* - 1 = full animation (default)
|
|
143
|
+
* - 0.5 = half the movement distance
|
|
144
|
+
* - 0 = no movement (instant appear)
|
|
145
|
+
* @default 1
|
|
146
|
+
*/
|
|
147
|
+
intensity?: number;
|
|
86
148
|
}
|
|
87
149
|
/**
|
|
88
150
|
* PuffPop - Animate children with beautiful entrance effects
|
|
89
151
|
*/
|
|
90
|
-
export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, onAnimationStart, style, animateOnMount, loop, loopDelay, respectReduceMotion, testID, }: PuffPopProps): ReactElement;
|
|
152
|
+
export declare function PuffPop({ children, effect, duration, delay, easing, skeleton, visible, onAnimationComplete, onAnimationStart, style, animateOnMount, loop, loopDelay, respectReduceMotion, testID, exitEffect, exitDuration, exitEasing, exitDelay, initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY, reverse, intensity, }: PuffPopProps): ReactElement;
|
|
91
153
|
/**
|
|
92
154
|
* Props for PuffPopGroup component
|
|
93
155
|
*/
|
|
@@ -175,6 +237,57 @@ export interface PuffPopGroupProps {
|
|
|
175
237
|
* Gap between children (uses flexbox gap)
|
|
176
238
|
*/
|
|
177
239
|
gap?: number;
|
|
240
|
+
/**
|
|
241
|
+
* Custom initial opacity value (0-1) for all children
|
|
242
|
+
*/
|
|
243
|
+
initialOpacity?: number;
|
|
244
|
+
/**
|
|
245
|
+
* Custom initial scale value for all children
|
|
246
|
+
*/
|
|
247
|
+
initialScale?: number;
|
|
248
|
+
/**
|
|
249
|
+
* Custom initial rotation value in degrees for all children
|
|
250
|
+
*/
|
|
251
|
+
initialRotate?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Custom initial translateX value in pixels for all children
|
|
254
|
+
*/
|
|
255
|
+
initialTranslateX?: number;
|
|
256
|
+
/**
|
|
257
|
+
* Custom initial translateY value in pixels for all children
|
|
258
|
+
*/
|
|
259
|
+
initialTranslateY?: number;
|
|
260
|
+
/**
|
|
261
|
+
* If true, reverses the animation direction for all children
|
|
262
|
+
* @default false
|
|
263
|
+
*/
|
|
264
|
+
reverse?: boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Animation intensity multiplier (0-1) for all children
|
|
267
|
+
* Controls how far/much elements move during animation
|
|
268
|
+
* @default 1
|
|
269
|
+
*/
|
|
270
|
+
intensity?: number;
|
|
271
|
+
/**
|
|
272
|
+
* Animation effect type when hiding (exit animation) for all children
|
|
273
|
+
* If not specified, uses the reverse of the enter effect
|
|
274
|
+
*/
|
|
275
|
+
exitEffect?: PuffPopEffect;
|
|
276
|
+
/**
|
|
277
|
+
* Animation duration for exit animation in milliseconds for all children
|
|
278
|
+
* If not specified, uses the same duration as enter animation
|
|
279
|
+
*/
|
|
280
|
+
exitDuration?: number;
|
|
281
|
+
/**
|
|
282
|
+
* Easing function for exit animation for all children
|
|
283
|
+
* If not specified, uses the same easing as enter animation
|
|
284
|
+
*/
|
|
285
|
+
exitEasing?: PuffPopEasing;
|
|
286
|
+
/**
|
|
287
|
+
* Delay before exit animation starts in milliseconds
|
|
288
|
+
* @default 0
|
|
289
|
+
*/
|
|
290
|
+
exitDelay?: number;
|
|
178
291
|
}
|
|
179
292
|
/**
|
|
180
293
|
* PuffPopGroup - Animate multiple children with staggered entrance effects
|
|
@@ -188,5 +301,5 @@ export interface PuffPopGroupProps {
|
|
|
188
301
|
* </PuffPopGroup>
|
|
189
302
|
* ```
|
|
190
303
|
*/
|
|
191
|
-
export declare function PuffPopGroup({ children, effect, duration, staggerDelay, initialDelay, easing, skeleton, visible, animateOnMount, onAnimationComplete, onAnimationStart, style, respectReduceMotion, testID, staggerDirection, horizontal, gap, }: PuffPopGroupProps): ReactElement;
|
|
304
|
+
export declare function PuffPopGroup({ children, effect, duration, staggerDelay, initialDelay, easing, skeleton, visible, animateOnMount, onAnimationComplete, onAnimationStart, style, respectReduceMotion, testID, staggerDirection, horizontal, gap, initialOpacity, initialScale, initialRotate, initialTranslateX, initialTranslateY, reverse, intensity, exitEffect, exitDuration, exitEasing, exitDelay, }: PuffPopGroupProps): ReactElement;
|
|
192
305
|
export default PuffPop;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-puff-pop",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "A React Native animation library for revealing children components with beautiful puff and pop effects",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/index.tsx
CHANGED
|
@@ -135,6 +135,87 @@ export interface PuffPopProps {
|
|
|
135
135
|
* Test ID for testing purposes
|
|
136
136
|
*/
|
|
137
137
|
testID?: string;
|
|
138
|
+
|
|
139
|
+
// ============ Exit Animation Settings ============
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Animation effect type when hiding (exit animation)
|
|
143
|
+
* If not specified, uses the reverse of the enter effect
|
|
144
|
+
*/
|
|
145
|
+
exitEffect?: PuffPopEffect;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Animation duration for exit animation in milliseconds
|
|
149
|
+
* If not specified, uses the same duration as enter animation
|
|
150
|
+
*/
|
|
151
|
+
exitDuration?: number;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Easing function for exit animation
|
|
155
|
+
* If not specified, uses the same easing as enter animation
|
|
156
|
+
*/
|
|
157
|
+
exitEasing?: PuffPopEasing;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Delay before exit animation starts in milliseconds
|
|
161
|
+
* @default 0
|
|
162
|
+
*/
|
|
163
|
+
exitDelay?: number;
|
|
164
|
+
|
|
165
|
+
// ============ Custom Initial Values ============
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Custom initial opacity value (0-1)
|
|
169
|
+
* Overrides the default initial opacity for the effect
|
|
170
|
+
*/
|
|
171
|
+
initialOpacity?: number;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Custom initial scale value
|
|
175
|
+
* Overrides the default initial scale for effects like 'scale', 'zoom', 'bounce'
|
|
176
|
+
*/
|
|
177
|
+
initialScale?: number;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Custom initial rotation value in degrees
|
|
181
|
+
* Overrides the default initial rotation for effects like 'rotate', 'rotateScale'
|
|
182
|
+
*/
|
|
183
|
+
initialRotate?: number;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Custom initial translateX value in pixels
|
|
187
|
+
* Overrides the default initial translateX for effects like 'slideLeft', 'slideRight'
|
|
188
|
+
*/
|
|
189
|
+
initialTranslateX?: number;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Custom initial translateY value in pixels
|
|
193
|
+
* Overrides the default initial translateY for effects like 'slideUp', 'slideDown'
|
|
194
|
+
*/
|
|
195
|
+
initialTranslateY?: number;
|
|
196
|
+
|
|
197
|
+
// ============ Reverse Mode ============
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* If true, reverses the animation direction
|
|
201
|
+
* - slideUp becomes slide from top
|
|
202
|
+
* - slideLeft becomes slide from left
|
|
203
|
+
* - rotate spins clockwise instead of counter-clockwise
|
|
204
|
+
* @default false
|
|
205
|
+
*/
|
|
206
|
+
reverse?: boolean;
|
|
207
|
+
|
|
208
|
+
// ============ Animation Intensity ============
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Animation intensity multiplier (0-1)
|
|
212
|
+
* Controls how far/much elements move during animation
|
|
213
|
+
* - 1 = full animation (default)
|
|
214
|
+
* - 0.5 = half the movement distance
|
|
215
|
+
* - 0 = no movement (instant appear)
|
|
216
|
+
* @default 1
|
|
217
|
+
*/
|
|
218
|
+
intensity?: number;
|
|
138
219
|
}
|
|
139
220
|
|
|
140
221
|
/**
|
|
@@ -178,13 +259,52 @@ export function PuffPop({
|
|
|
178
259
|
loopDelay = 0,
|
|
179
260
|
respectReduceMotion = true,
|
|
180
261
|
testID,
|
|
262
|
+
// Exit animation settings
|
|
263
|
+
exitEffect,
|
|
264
|
+
exitDuration,
|
|
265
|
+
exitEasing,
|
|
266
|
+
exitDelay = 0,
|
|
267
|
+
// Custom initial values
|
|
268
|
+
initialOpacity,
|
|
269
|
+
initialScale,
|
|
270
|
+
initialRotate,
|
|
271
|
+
initialTranslateX,
|
|
272
|
+
initialTranslateY,
|
|
273
|
+
// Reverse mode
|
|
274
|
+
reverse = false,
|
|
275
|
+
// Animation intensity
|
|
276
|
+
intensity = 1,
|
|
181
277
|
}: PuffPopProps): ReactElement {
|
|
278
|
+
// Clamp intensity between 0 and 1
|
|
279
|
+
const clampedIntensity = Math.max(0, Math.min(1, intensity));
|
|
280
|
+
|
|
281
|
+
// Helper to get initial value with custom override, reverse, and intensity support
|
|
282
|
+
const getInitialOpacityValue = () => initialOpacity ?? 0;
|
|
283
|
+
const getInitialScaleValue = (eff: PuffPopEffect) => {
|
|
284
|
+
if (initialScale !== undefined) return initialScale;
|
|
285
|
+
const baseScale = getInitialScale(eff, reverse);
|
|
286
|
+
// Scale goes from baseScale to 1, so we interpolate: 1 - (1 - baseScale) * intensity
|
|
287
|
+
return 1 - (1 - baseScale) * clampedIntensity;
|
|
288
|
+
};
|
|
289
|
+
const getInitialRotateValue = (eff: PuffPopEffect) => {
|
|
290
|
+
if (initialRotate !== undefined) return initialRotate;
|
|
291
|
+
return getInitialRotate(eff, reverse) * clampedIntensity;
|
|
292
|
+
};
|
|
293
|
+
const getInitialTranslateXValue = (eff: PuffPopEffect) => {
|
|
294
|
+
if (initialTranslateX !== undefined) return initialTranslateX;
|
|
295
|
+
return getInitialTranslateX(eff, reverse) * clampedIntensity;
|
|
296
|
+
};
|
|
297
|
+
const getInitialTranslateYValue = (eff: PuffPopEffect) => {
|
|
298
|
+
if (initialTranslateY !== undefined) return initialTranslateY;
|
|
299
|
+
return getInitialTranslateY(eff, reverse) * clampedIntensity;
|
|
300
|
+
};
|
|
301
|
+
|
|
182
302
|
// Animation values
|
|
183
|
-
const opacity = useRef(new Animated.Value(animateOnMount ?
|
|
184
|
-
const scale = useRef(new Animated.Value(animateOnMount ?
|
|
185
|
-
const rotate = useRef(new Animated.Value(animateOnMount ?
|
|
186
|
-
const translateX = useRef(new Animated.Value(animateOnMount ?
|
|
187
|
-
const translateY = useRef(new Animated.Value(animateOnMount ?
|
|
303
|
+
const opacity = useRef(new Animated.Value(animateOnMount ? getInitialOpacityValue() : 1)).current;
|
|
304
|
+
const scale = useRef(new Animated.Value(animateOnMount ? getInitialScaleValue(effect) : 1)).current;
|
|
305
|
+
const rotate = useRef(new Animated.Value(animateOnMount ? getInitialRotateValue(effect) : 0)).current;
|
|
306
|
+
const translateX = useRef(new Animated.Value(animateOnMount ? getInitialTranslateXValue(effect) : 0)).current;
|
|
307
|
+
const translateY = useRef(new Animated.Value(animateOnMount ? getInitialTranslateYValue(effect) : 0)).current;
|
|
188
308
|
|
|
189
309
|
// For non-skeleton mode
|
|
190
310
|
const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);
|
|
@@ -218,16 +338,25 @@ export function PuffPop({
|
|
|
218
338
|
|
|
219
339
|
// Effective duration (0 if reduce motion is enabled)
|
|
220
340
|
const effectiveDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : duration;
|
|
341
|
+
const effectiveExitDuration = respectReduceMotion && isReduceMotionEnabled ? 0 : (exitDuration ?? duration);
|
|
342
|
+
|
|
343
|
+
// Helper to get effect flags for any effect type
|
|
344
|
+
const getEffectFlags = useCallback((eff: PuffPopEffect) => ({
|
|
345
|
+
hasScale: ['scale', 'bounce', 'zoom', 'rotateScale', 'flip'].includes(eff),
|
|
346
|
+
hasRotate: ['rotate', 'rotateScale'].includes(eff),
|
|
347
|
+
hasFlip: eff === 'flip',
|
|
348
|
+
hasTranslateX: ['slideLeft', 'slideRight'].includes(eff),
|
|
349
|
+
hasTranslateY: ['slideUp', 'slideDown', 'bounce'].includes(eff),
|
|
350
|
+
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(eff),
|
|
351
|
+
}), []);
|
|
221
352
|
|
|
222
353
|
// Memoize effect type checks to avoid repeated includes() calls
|
|
223
|
-
const effectFlags = useMemo(() => (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
hasRotateEffect: ['rotate', 'rotateScale', 'flip'].includes(effect),
|
|
230
|
-
}), [effect]);
|
|
354
|
+
const effectFlags = useMemo(() => getEffectFlags(effect), [effect, getEffectFlags]);
|
|
355
|
+
|
|
356
|
+
// Exit effect flags (use exitEffect if specified, otherwise same as enter effect)
|
|
357
|
+
const exitEffectFlags = useMemo(() =>
|
|
358
|
+
exitEffect ? getEffectFlags(exitEffect) : effectFlags
|
|
359
|
+
, [exitEffect, getEffectFlags, effectFlags]);
|
|
231
360
|
|
|
232
361
|
// Memoize interpolations to avoid recreating on every render
|
|
233
362
|
const rotateInterpolation = useMemo(() =>
|
|
@@ -261,12 +390,19 @@ export function PuffPop({
|
|
|
261
390
|
onAnimationStart();
|
|
262
391
|
}
|
|
263
392
|
|
|
264
|
-
|
|
393
|
+
// Determine which effect settings to use based on direction
|
|
394
|
+
const currentEffect = toVisible ? effect : (exitEffect ?? effect);
|
|
395
|
+
const currentDuration = toVisible ? effectiveDuration : effectiveExitDuration;
|
|
396
|
+
const currentEasing = toVisible ? easing : (exitEasing ?? easing);
|
|
397
|
+
const currentDelay = toVisible ? delay : exitDelay;
|
|
398
|
+
const currentFlags = toVisible ? effectFlags : exitEffectFlags;
|
|
399
|
+
|
|
400
|
+
const easingFn = getEasing(currentEasing);
|
|
265
401
|
// When skeleton is false, we animate height which doesn't support native driver
|
|
266
402
|
// So we must use JS driver for all animations in that case
|
|
267
403
|
const useNative = skeleton;
|
|
268
404
|
const config = {
|
|
269
|
-
duration:
|
|
405
|
+
duration: currentDuration,
|
|
270
406
|
easing: easingFn,
|
|
271
407
|
useNativeDriver: useNative,
|
|
272
408
|
};
|
|
@@ -282,20 +418,20 @@ export function PuffPop({
|
|
|
282
418
|
);
|
|
283
419
|
|
|
284
420
|
// Scale animation
|
|
285
|
-
if (
|
|
286
|
-
const targetScale = toVisible ? 1 :
|
|
421
|
+
if (currentFlags.hasScale) {
|
|
422
|
+
const targetScale = toVisible ? 1 : getInitialScaleValue(currentEffect);
|
|
287
423
|
animations.push(
|
|
288
424
|
Animated.timing(scale, {
|
|
289
425
|
toValue: targetScale,
|
|
290
426
|
...config,
|
|
291
|
-
easing:
|
|
427
|
+
easing: currentEffect === 'bounce' ? Easing.bounce : easingFn,
|
|
292
428
|
})
|
|
293
429
|
);
|
|
294
430
|
}
|
|
295
431
|
|
|
296
432
|
// Rotate animation
|
|
297
|
-
if (
|
|
298
|
-
const targetRotate = toVisible ? 0 :
|
|
433
|
+
if (currentFlags.hasRotate || currentFlags.hasFlip) {
|
|
434
|
+
const targetRotate = toVisible ? 0 : getInitialRotateValue(currentEffect);
|
|
299
435
|
animations.push(
|
|
300
436
|
Animated.timing(rotate, {
|
|
301
437
|
toValue: targetRotate,
|
|
@@ -305,8 +441,8 @@ export function PuffPop({
|
|
|
305
441
|
}
|
|
306
442
|
|
|
307
443
|
// TranslateX animation
|
|
308
|
-
if (
|
|
309
|
-
const targetX = toVisible ? 0 :
|
|
444
|
+
if (currentFlags.hasTranslateX) {
|
|
445
|
+
const targetX = toVisible ? 0 : getInitialTranslateXValue(currentEffect);
|
|
310
446
|
animations.push(
|
|
311
447
|
Animated.timing(translateX, {
|
|
312
448
|
toValue: targetX,
|
|
@@ -316,8 +452,8 @@ export function PuffPop({
|
|
|
316
452
|
}
|
|
317
453
|
|
|
318
454
|
// TranslateY animation
|
|
319
|
-
if (
|
|
320
|
-
const targetY = toVisible ? 0 :
|
|
455
|
+
if (currentFlags.hasTranslateY) {
|
|
456
|
+
const targetY = toVisible ? 0 : getInitialTranslateYValue(currentEffect);
|
|
321
457
|
animations.push(
|
|
322
458
|
Animated.timing(translateY, {
|
|
323
459
|
toValue: targetY,
|
|
@@ -332,7 +468,7 @@ export function PuffPop({
|
|
|
332
468
|
animations.push(
|
|
333
469
|
Animated.timing(animatedHeight, {
|
|
334
470
|
toValue: targetHeight,
|
|
335
|
-
duration:
|
|
471
|
+
duration: currentDuration,
|
|
336
472
|
easing: easingFn,
|
|
337
473
|
useNativeDriver: false,
|
|
338
474
|
})
|
|
@@ -344,11 +480,11 @@ export function PuffPop({
|
|
|
344
480
|
|
|
345
481
|
// Reset values function for looping
|
|
346
482
|
const resetValues = () => {
|
|
347
|
-
opacity.setValue(
|
|
348
|
-
scale.setValue(
|
|
349
|
-
rotate.setValue(
|
|
350
|
-
translateX.setValue(
|
|
351
|
-
translateY.setValue(
|
|
483
|
+
opacity.setValue(getInitialOpacityValue());
|
|
484
|
+
scale.setValue(getInitialScaleValue(effect));
|
|
485
|
+
rotate.setValue(getInitialRotateValue(effect));
|
|
486
|
+
translateX.setValue(getInitialTranslateXValue(effect));
|
|
487
|
+
translateY.setValue(getInitialTranslateYValue(effect));
|
|
352
488
|
if (!skeleton && measuredHeight !== null) {
|
|
353
489
|
animatedHeight.setValue(0);
|
|
354
490
|
}
|
|
@@ -357,9 +493,9 @@ export function PuffPop({
|
|
|
357
493
|
// Build the animation sequence
|
|
358
494
|
let animation: Animated.CompositeAnimation;
|
|
359
495
|
|
|
360
|
-
if (
|
|
496
|
+
if (currentDelay > 0) {
|
|
361
497
|
animation = Animated.sequence([
|
|
362
|
-
Animated.delay(
|
|
498
|
+
Animated.delay(currentDelay),
|
|
363
499
|
parallelAnimation,
|
|
364
500
|
]);
|
|
365
501
|
} else {
|
|
@@ -430,9 +566,14 @@ export function PuffPop({
|
|
|
430
566
|
[
|
|
431
567
|
delay,
|
|
432
568
|
effectiveDuration,
|
|
569
|
+
effectiveExitDuration,
|
|
433
570
|
easing,
|
|
434
571
|
effect,
|
|
435
572
|
effectFlags,
|
|
573
|
+
exitEffect,
|
|
574
|
+
exitEffectFlags,
|
|
575
|
+
exitEasing,
|
|
576
|
+
exitDelay,
|
|
436
577
|
measuredHeight,
|
|
437
578
|
onAnimationComplete,
|
|
438
579
|
onAnimationStart,
|
|
@@ -543,7 +684,8 @@ export function PuffPop({
|
|
|
543
684
|
/**
|
|
544
685
|
* Get initial scale value based on effect
|
|
545
686
|
*/
|
|
546
|
-
function getInitialScale(effect: PuffPopEffect): number {
|
|
687
|
+
function getInitialScale(effect: PuffPopEffect, _reverse = false): number {
|
|
688
|
+
// Scale doesn't change with reverse (parameter kept for consistent API)
|
|
547
689
|
switch (effect) {
|
|
548
690
|
case 'scale':
|
|
549
691
|
case 'rotateScale':
|
|
@@ -562,14 +704,15 @@ function getInitialScale(effect: PuffPopEffect): number {
|
|
|
562
704
|
/**
|
|
563
705
|
* Get initial rotate value based on effect
|
|
564
706
|
*/
|
|
565
|
-
function getInitialRotate(effect: PuffPopEffect): number {
|
|
707
|
+
function getInitialRotate(effect: PuffPopEffect, reverse = false): number {
|
|
708
|
+
const multiplier = reverse ? -1 : 1;
|
|
566
709
|
switch (effect) {
|
|
567
710
|
case 'rotate':
|
|
568
|
-
return -360;
|
|
711
|
+
return -360 * multiplier;
|
|
569
712
|
case 'rotateScale':
|
|
570
|
-
return -180;
|
|
713
|
+
return -180 * multiplier;
|
|
571
714
|
case 'flip':
|
|
572
|
-
return -180;
|
|
715
|
+
return -180 * multiplier;
|
|
573
716
|
default:
|
|
574
717
|
return 0;
|
|
575
718
|
}
|
|
@@ -578,12 +721,13 @@ function getInitialRotate(effect: PuffPopEffect): number {
|
|
|
578
721
|
/**
|
|
579
722
|
* Get initial translateX value based on effect
|
|
580
723
|
*/
|
|
581
|
-
function getInitialTranslateX(effect: PuffPopEffect): number {
|
|
724
|
+
function getInitialTranslateX(effect: PuffPopEffect, reverse = false): number {
|
|
725
|
+
const multiplier = reverse ? -1 : 1;
|
|
582
726
|
switch (effect) {
|
|
583
727
|
case 'slideLeft':
|
|
584
|
-
return 100;
|
|
728
|
+
return 100 * multiplier;
|
|
585
729
|
case 'slideRight':
|
|
586
|
-
return -100;
|
|
730
|
+
return -100 * multiplier;
|
|
587
731
|
default:
|
|
588
732
|
return 0;
|
|
589
733
|
}
|
|
@@ -592,14 +736,15 @@ function getInitialTranslateX(effect: PuffPopEffect): number {
|
|
|
592
736
|
/**
|
|
593
737
|
* Get initial translateY value based on effect
|
|
594
738
|
*/
|
|
595
|
-
function getInitialTranslateY(effect: PuffPopEffect): number {
|
|
739
|
+
function getInitialTranslateY(effect: PuffPopEffect, reverse = false): number {
|
|
740
|
+
const multiplier = reverse ? -1 : 1;
|
|
596
741
|
switch (effect) {
|
|
597
742
|
case 'slideUp':
|
|
598
|
-
return 50;
|
|
743
|
+
return 50 * multiplier;
|
|
599
744
|
case 'slideDown':
|
|
600
|
-
return -50;
|
|
745
|
+
return -50 * multiplier;
|
|
601
746
|
case 'bounce':
|
|
602
|
-
return 30;
|
|
747
|
+
return 30 * multiplier;
|
|
603
748
|
default:
|
|
604
749
|
return 0;
|
|
605
750
|
}
|
|
@@ -721,6 +866,76 @@ export interface PuffPopGroupProps {
|
|
|
721
866
|
* Gap between children (uses flexbox gap)
|
|
722
867
|
*/
|
|
723
868
|
gap?: number;
|
|
869
|
+
|
|
870
|
+
// ============ Custom Initial Values ============
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Custom initial opacity value (0-1) for all children
|
|
874
|
+
*/
|
|
875
|
+
initialOpacity?: number;
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Custom initial scale value for all children
|
|
879
|
+
*/
|
|
880
|
+
initialScale?: number;
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Custom initial rotation value in degrees for all children
|
|
884
|
+
*/
|
|
885
|
+
initialRotate?: number;
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Custom initial translateX value in pixels for all children
|
|
889
|
+
*/
|
|
890
|
+
initialTranslateX?: number;
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Custom initial translateY value in pixels for all children
|
|
894
|
+
*/
|
|
895
|
+
initialTranslateY?: number;
|
|
896
|
+
|
|
897
|
+
// ============ Reverse Mode ============
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* If true, reverses the animation direction for all children
|
|
901
|
+
* @default false
|
|
902
|
+
*/
|
|
903
|
+
reverse?: boolean;
|
|
904
|
+
|
|
905
|
+
// ============ Animation Intensity ============
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Animation intensity multiplier (0-1) for all children
|
|
909
|
+
* Controls how far/much elements move during animation
|
|
910
|
+
* @default 1
|
|
911
|
+
*/
|
|
912
|
+
intensity?: number;
|
|
913
|
+
|
|
914
|
+
// ============ Exit Animation Settings ============
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Animation effect type when hiding (exit animation) for all children
|
|
918
|
+
* If not specified, uses the reverse of the enter effect
|
|
919
|
+
*/
|
|
920
|
+
exitEffect?: PuffPopEffect;
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Animation duration for exit animation in milliseconds for all children
|
|
924
|
+
* If not specified, uses the same duration as enter animation
|
|
925
|
+
*/
|
|
926
|
+
exitDuration?: number;
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Easing function for exit animation for all children
|
|
930
|
+
* If not specified, uses the same easing as enter animation
|
|
931
|
+
*/
|
|
932
|
+
exitEasing?: PuffPopEasing;
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Delay before exit animation starts in milliseconds
|
|
936
|
+
* @default 0
|
|
937
|
+
*/
|
|
938
|
+
exitDelay?: number;
|
|
724
939
|
}
|
|
725
940
|
|
|
726
941
|
/**
|
|
@@ -753,6 +968,21 @@ export function PuffPopGroup({
|
|
|
753
968
|
staggerDirection = 'forward',
|
|
754
969
|
horizontal = false,
|
|
755
970
|
gap,
|
|
971
|
+
// Custom initial values
|
|
972
|
+
initialOpacity,
|
|
973
|
+
initialScale,
|
|
974
|
+
initialRotate,
|
|
975
|
+
initialTranslateX,
|
|
976
|
+
initialTranslateY,
|
|
977
|
+
// Reverse mode
|
|
978
|
+
reverse,
|
|
979
|
+
// Animation intensity
|
|
980
|
+
intensity,
|
|
981
|
+
// Exit animation settings
|
|
982
|
+
exitEffect,
|
|
983
|
+
exitDuration,
|
|
984
|
+
exitEasing,
|
|
985
|
+
exitDelay,
|
|
756
986
|
}: PuffPopGroupProps): ReactElement {
|
|
757
987
|
const childArray = Children.toArray(children);
|
|
758
988
|
const childCount = childArray.length;
|
|
@@ -838,6 +1068,17 @@ export function PuffPopGroup({
|
|
|
838
1068
|
onAnimationComplete={handleChildComplete}
|
|
839
1069
|
onAnimationStart={index === 0 ? handleChildStart : undefined}
|
|
840
1070
|
respectReduceMotion={respectReduceMotion}
|
|
1071
|
+
initialOpacity={initialOpacity}
|
|
1072
|
+
initialScale={initialScale}
|
|
1073
|
+
initialRotate={initialRotate}
|
|
1074
|
+
initialTranslateX={initialTranslateX}
|
|
1075
|
+
initialTranslateY={initialTranslateY}
|
|
1076
|
+
reverse={reverse}
|
|
1077
|
+
intensity={intensity}
|
|
1078
|
+
exitEffect={exitEffect}
|
|
1079
|
+
exitDuration={exitDuration}
|
|
1080
|
+
exitEasing={exitEasing}
|
|
1081
|
+
exitDelay={exitDelay}
|
|
841
1082
|
>
|
|
842
1083
|
{child}
|
|
843
1084
|
</PuffPop>
|