Skip to content

Commit 848f458

Browse files
authored
feat: add index parameter to customAnimation (#735)
<!-- Is this PR related to an open issue? --> <!-- GitHub: Fixes #0, Relates to #1, etc. --> ### Description <!-- Summary of changes and why if no corresponding issue --> #709 ### Review - [ ] I self-reviewed this PR <!-- Call out any changes that you'd like people to review or feedback on --> ### Testing - [ ] I added/updated tests - [ ] I manually tested <!-- Describe any manual testing -->
1 parent 1e62129 commit 848f458

File tree

5 files changed

+67
-9
lines changed

5 files changed

+67
-9
lines changed

.changeset/modern-carpets-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-native-reanimated-carousel": patch
3+
---
4+
5+
This PR updates the customAnimation function signature to include an index parameter, allowing users to apply custom animations based on the item’s index.

src/components/Carousel.test.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { FC } from "react";
22
import React from "react";
33
import type { PanGesture } from "react-native-gesture-handler";
44
import { Gesture, State } from "react-native-gesture-handler";
5-
import Animated, { useDerivedValue, useSharedValue } from "react-native-reanimated";
5+
import Animated, { interpolate, useDerivedValue, useSharedValue } from "react-native-reanimated";
66
import type { ReactTestInstance } from "react-test-renderer";
77

88
import { act, render, waitFor } from "@testing-library/react-native";
@@ -301,7 +301,11 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp
301301

302302
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
303303
{ state: State.BEGAN, translationX: 0, velocityX: -5 },
304-
{ state: State.ACTIVE, translationX: -slideWidth * 0.15, velocityX: -5 },
304+
{
305+
state: State.ACTIVE,
306+
translationX: -slideWidth * 0.15,
307+
velocityX: -5,
308+
},
305309
{ state: State.END, translationX: -slideWidth * 0.25, velocityX: -5 },
306310
]);
307311

@@ -314,8 +318,16 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp
314318

315319
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
316320
{ state: State.BEGAN, translationX: 0, velocityX: -1000 },
317-
{ state: State.ACTIVE, translationX: -slideWidth * 0.15, velocityX: -1000 },
318-
{ state: State.END, translationX: -slideWidth * 0.25, velocityX: -1000 },
321+
{
322+
state: State.ACTIVE,
323+
translationX: -slideWidth * 0.15,
324+
velocityX: -1000,
325+
},
326+
{
327+
state: State.END,
328+
translationX: -slideWidth * 0.25,
329+
velocityX: -1000,
330+
},
319331
]);
320332

321333
await waitFor(() => expect(progress.current).toBe(1));
@@ -441,6 +453,45 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp
441453
}
442454
});
443455

456+
it("`customAnimation` prop: should apply the custom animation", async () => {
457+
const progress = { current: 0 };
458+
const indexes: Record<number, number> = {};
459+
const Wrapper = createCarousel(progress);
460+
const { getByTestId } = render(
461+
<Wrapper
462+
customAnimation={(value: number, index: number) => {
463+
"worklet";
464+
465+
indexes[index] = index;
466+
467+
const zIndex = interpolate(value, [-1, 0, 1], [10, 20, 30]);
468+
const translateX = interpolate(value, [-2, 0, 1], [-slideWidth, 0, slideWidth]);
469+
470+
return {
471+
transform: [{ translateX }],
472+
zIndex,
473+
};
474+
}}
475+
/>
476+
);
477+
478+
await verifyInitialRender(getByTestId);
479+
480+
swipeToLeftOnce();
481+
await waitFor(() => {
482+
expect(progress.current).toBe(1);
483+
484+
expect(indexes).toMatchInlineSnapshot(`
485+
{
486+
"0": 0,
487+
"1": 1,
488+
"2": 2,
489+
"3": 3,
490+
}
491+
`);
492+
});
493+
});
494+
444495
it("`overscrollEnabled` prop: should respect overscrollEnabled=false and prevent scrolling beyond bounds", async () => {
445496
const containerWidth = slideWidth;
446497
const containerHeight = containerWidth / 2;

src/components/ItemLayout.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import type { ViewStyle } from "react-native";
33
import type { SharedValue } from "react-native-reanimated";
44
import Animated, { useAnimatedStyle, useDerivedValue } from "react-native-reanimated";
55

6+
import { TCarouselProps } from "src/types";
67
import type { IOpts } from "../hooks/useOffsetX";
78
import { useOffsetX } from "../hooks/useOffsetX";
89
import type { IVisibleRanges } from "../hooks/useVisibleRanges";
910
import type { ILayoutConfig } from "../layouts/stack";
1011
import { useGlobalState } from "../store";
1112

12-
export type TAnimationStyle = (value: number) => ViewStyle;
13+
export type TAnimationStyle = NonNullable<TCarouselProps["customAnimation"]>;
1314

1415
export const ItemLayout: React.FC<{
1516
index: number;
@@ -56,8 +57,8 @@ export const ItemLayout: React.FC<{
5657
const x = useOffsetX(offsetXConfig, visibleRanges);
5758
const animationValue = useDerivedValue(() => x.value / size, [x, size]);
5859
const animatedStyle = useAnimatedStyle<ViewStyle>(
59-
() => animationStyle(x.value / size),
60-
[animationStyle]
60+
() => animationStyle(x.value / size, index),
61+
[animationStyle, index]
6162
);
6263

6364
// TODO: For dynamic dimension in the future

src/components/ItemRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface Props {
2424
handlerOffset: SharedValue<number>;
2525
layoutConfig: TAnimationStyle;
2626
renderItem: CarouselRenderItem<any>;
27-
customAnimation?: (value: number) => ViewStyle;
27+
customAnimation?: (value: number, index: number) => ViewStyle;
2828
}
2929

3030
export const ItemRenderer: FC<Props> = (props) => {

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,9 @@ export type TCarouselProps<T = any> = {
187187
/**
188188
* Custom animations.
189189
* Must use `worklet`, Details: https://docs.swmansion.com/react-native-reanimated/docs/2.2.0/worklets/
190+
* @test_coverage ✅ tested in Carousel.test.tsx > should apply the custom animation
190191
*/
191-
customAnimation?: (value: number) => ViewStyle;
192+
customAnimation?: (value: number, index: number) => ViewStyle;
192193
/**
193194
* Render carousel item.
194195
* @test_coverage ✅ tested in Carousel.test.tsx > should render items correctly

0 commit comments

Comments
 (0)