Skip to content

Commit ad12d8c

Browse files
authored
Add swipeCard method to DeckSwiper components (#959)
* Add `swipeCard` method to DeckSwiper components * Add default case for swiping
1 parent 39b0a3b commit ad12d8c

File tree

3 files changed

+159
-128
lines changed

3 files changed

+159
-128
lines changed

packages/core/src/components/DeckSwiper/DeckSwiper.tsx

Lines changed: 157 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import React from "react";
22
import { StyleProp, ViewStyle, StyleSheet, View } from "react-native";
33
import DeckSwiperComponent from "react-native-deck-swiper";
44

5+
export interface DeckSwiperRef {
6+
swipeCard: (direction: "left" | "right" | "top" | "bottom") => void;
7+
}
8+
59
export interface DeckSwiperProps<T> {
610
onStartSwipe?: () => void;
711
onEndSwipe?: () => void;
@@ -23,145 +27,171 @@ export interface DeckSwiperProps<T> {
2327
style?: StyleProp<ViewStyle>;
2428
}
2529

26-
const DeckSwiper = <T extends object>({
27-
onStartSwipe,
28-
onEndSwipe,
29-
onSwipe,
30-
onSwipedLeft,
31-
onSwipedRight,
32-
onSwipedUp,
33-
onSwipedDown,
34-
onIndexChanged,
35-
onEndReached,
36-
startCardIndex = 0,
37-
infiniteSwiping = false,
38-
verticalEnabled = true,
39-
horizontalEnabled = true,
40-
visibleCardCount = 1,
41-
data,
42-
keyExtractor,
43-
renderItem,
44-
style,
45-
children,
46-
}: React.PropsWithChildren<DeckSwiperProps<T>>) => {
47-
//Both 'renderItem' and 'data' are optional to allow direct children. But if one is included, both need to be included
48-
if ((data && !renderItem) || (renderItem && !data)) {
49-
throw new Error(
50-
"'renderItem' and 'data' need to both be provided to lazily render. Either remove them entirley or include both"
51-
);
52-
}
30+
const DeckSwiper = React.forwardRef<DeckSwiperRef, DeckSwiperProps<any>>(
31+
<T extends object>(
32+
{
33+
onStartSwipe,
34+
onEndSwipe,
35+
onSwipe,
36+
onSwipedLeft,
37+
onSwipedRight,
38+
onSwipedUp,
39+
onSwipedDown,
40+
onIndexChanged,
41+
onEndReached,
42+
startCardIndex = 0,
43+
infiniteSwiping = false,
44+
verticalEnabled = true,
45+
horizontalEnabled = true,
46+
visibleCardCount = 1,
47+
data,
48+
keyExtractor,
49+
renderItem,
50+
style,
51+
children,
52+
}: React.PropsWithChildren<DeckSwiperProps<T>>,
53+
ref: React.Ref<DeckSwiperRef>
54+
) => {
55+
//Both 'renderItem' and 'data' are optional to allow direct children. But if one is included, both need to be included
56+
if ((data && !renderItem) || (renderItem && !data)) {
57+
throw new Error(
58+
"'renderItem' and 'data' need to both be provided to lazily render. Either remove them entirley or include both"
59+
);
60+
}
5361

54-
if (data && renderItem && children) {
55-
console.warn(
56-
"'children' of DeckSwiper ignored due to usage of 'data' and 'renderItem'"
57-
);
58-
}
62+
if (data && renderItem && children) {
63+
console.warn(
64+
"'children' of DeckSwiper ignored due to usage of 'data' and 'renderItem'"
65+
);
66+
}
5967

60-
const deckSwiperRef = React.useRef<DeckSwiperComponent<T>>(null);
68+
const deckSwiperRef = React.useRef<DeckSwiperComponent<T>>(null);
6169

62-
const childrenArray = React.useMemo(
63-
() => React.Children.toArray(children),
64-
[children]
65-
);
70+
const childrenArray = React.useMemo(
71+
() => React.Children.toArray(children),
72+
[children]
73+
);
6674

67-
// an array of indices based on children count
68-
const cardsFillerData = React.useMemo(
69-
() => Array.from(Array(childrenArray.length).keys()),
70-
[childrenArray]
71-
);
75+
// an array of indices based on children count
76+
const cardsFillerData = React.useMemo(
77+
() => Array.from(Array(childrenArray.length).keys()),
78+
[childrenArray]
79+
);
7280

73-
const cardsData = Array.isArray(data) ? data : cardsFillerData;
81+
const cardsData = Array.isArray(data) ? data : cardsFillerData;
7482

75-
const renderCard = (card: any, index: number): JSX.Element => {
76-
if (renderItem) {
77-
return renderItem({ item: card, index });
78-
} else {
79-
return <>{childrenArray[index]}</>;
80-
}
81-
};
83+
const renderCard = (card: any, index: number): JSX.Element => {
84+
if (renderItem) {
85+
return renderItem({ item: card, index });
86+
} else {
87+
return <>{childrenArray[index]}</>;
88+
}
89+
};
8290

83-
const renderFirstCard = (): JSX.Element | undefined => {
84-
if (cardsData.length) {
85-
return renderCard(cardsData[0], 0);
86-
}
87-
return undefined;
88-
};
89-
90-
const cardKeyExtractor = (card: any) => {
91-
if (keyExtractor) {
92-
return keyExtractor(card);
93-
} else {
94-
return card?.toString();
95-
}
96-
};
91+
const renderFirstCard = (): JSX.Element | undefined => {
92+
if (cardsData.length) {
93+
return renderCard(cardsData[0], 0);
94+
}
95+
return undefined;
96+
};
9797

98-
/*
98+
const cardKeyExtractor = (card: any) => {
99+
if (keyExtractor) {
100+
return keyExtractor(card);
101+
} else {
102+
return card?.toString();
103+
}
104+
};
105+
106+
/*
99107
react-native-deck-swiper does not re-render cards when parent state changes
100108
This forces an update on every re-render to reflect any parent state changes
101109
*/
102-
React.useEffect(() => {
103-
deckSwiperRef.current?.forceUpdate();
104-
});
105-
106-
/**
107-
* By default react-native-deck-swiper positions everything with absolute position.
108-
* To overcome this, it is wrapped in a View to be able to add the component in any layout structure.
109-
*
110-
*
111-
* Since all children of that View are absolutley positioned, the View does not have a height and still looks and behaves weird.
112-
* To fix/mitage this without setting a static height, the first card is rendered in invisible state to take up space.
113-
* This effectivley makes the default height of the container be the height of the first card.
114-
*/
115-
116-
return (
117-
<View>
118-
<View style={styles.containerHeightFiller}>{renderFirstCard()}</View>
119-
<DeckSwiperComponent
120-
ref={deckSwiperRef}
121-
cards={cardsData as any[]}
122-
renderCard={renderCard}
123-
keyExtractor={cardKeyExtractor}
124-
containerStyle={
125-
StyleSheet.flatten([styles.cardsContainer, style]) as
126-
| object
127-
| undefined
110+
React.useEffect(() => {
111+
deckSwiperRef.current?.forceUpdate();
112+
});
113+
114+
React.useImperativeHandle(ref, () => ({
115+
swipeCard: (direction: "left" | "right" | "top" | "bottom") => {
116+
switch (direction) {
117+
case "left":
118+
deckSwiperRef.current?.swipeLeft();
119+
break;
120+
case "right":
121+
deckSwiperRef.current?.swipeRight();
122+
break;
123+
case "top":
124+
deckSwiperRef.current?.swipeTop();
125+
break;
126+
case "bottom":
127+
deckSwiperRef.current?.swipeBottom();
128+
break;
129+
default:
130+
deckSwiperRef.current?.swipeLeft();
128131
}
129-
cardStyle={styles.card as object | undefined}
130-
onSwiped={onIndexChanged}
131-
onSwipedAll={onEndReached}
132-
cardIndex={startCardIndex}
133-
infinite={infiniteSwiping}
134-
verticalSwipe={verticalEnabled}
135-
horizontalSwipe={horizontalEnabled}
136-
showSecondCard={visibleCardCount > 1}
137-
stackSize={visibleCardCount}
138-
backgroundColor="transparent"
139-
cardVerticalMargin={0}
140-
cardHorizontalMargin={0}
141-
onSwipedLeft={(index) => {
142-
onSwipedLeft?.(index);
143-
onSwipe?.(index);
144-
}}
145-
onSwipedRight={(index) => {
146-
onSwipedRight?.(index);
147-
onSwipe?.(index);
148-
}}
149-
onSwipedTop={(index) => {
150-
onSwipedUp?.(index);
151-
onSwipe?.(index);
152-
}}
153-
onSwipedBottom={(index) => {
154-
onSwipedDown?.(index);
155-
onSwipe?.(index);
156-
}}
157-
//@ts-ignore Not typed, but is implemented and works
158-
dragStart={onStartSwipe}
159-
//@ts-ignore
160-
dragEnd={onEndSwipe}
161-
/>
162-
</View>
163-
);
164-
};
132+
},
133+
}));
134+
135+
/**
136+
* By default react-native-deck-swiper positions everything with absolute position.
137+
* To overcome this, it is wrapped in a View to be able to add the component in any layout structure.
138+
*
139+
*
140+
* Since all children of that View are absolutley positioned, the View does not have a height and still looks and behaves weird.
141+
* To fix/mitage this without setting a static height, the first card is rendered in invisible state to take up space.
142+
* This effectivley makes the default height of the container be the height of the first card.
143+
*/
144+
145+
return (
146+
<View>
147+
<View style={styles.containerHeightFiller}>{renderFirstCard()}</View>
148+
<DeckSwiperComponent
149+
ref={deckSwiperRef}
150+
cards={cardsData as any[]}
151+
renderCard={renderCard}
152+
keyExtractor={cardKeyExtractor}
153+
containerStyle={
154+
StyleSheet.flatten([styles.cardsContainer, style]) as
155+
| object
156+
| undefined
157+
}
158+
cardStyle={styles.card as object | undefined}
159+
onSwiped={onIndexChanged}
160+
onSwipedAll={onEndReached}
161+
cardIndex={startCardIndex}
162+
infinite={infiniteSwiping}
163+
verticalSwipe={verticalEnabled}
164+
horizontalSwipe={horizontalEnabled}
165+
showSecondCard={visibleCardCount > 1}
166+
stackSize={visibleCardCount}
167+
backgroundColor="transparent"
168+
cardVerticalMargin={0}
169+
cardHorizontalMargin={0}
170+
onSwipedLeft={(index) => {
171+
onSwipedLeft?.(index);
172+
onSwipe?.(index);
173+
}}
174+
onSwipedRight={(index) => {
175+
onSwipedRight?.(index);
176+
onSwipe?.(index);
177+
}}
178+
onSwipedTop={(index) => {
179+
onSwipedUp?.(index);
180+
onSwipe?.(index);
181+
}}
182+
onSwipedBottom={(index) => {
183+
onSwipedDown?.(index);
184+
onSwipe?.(index);
185+
}}
186+
//@ts-ignore Not typed, but is implemented and works
187+
dragStart={onStartSwipe}
188+
//@ts-ignore
189+
dragEnd={onEndSwipe}
190+
/>
191+
</View>
192+
);
193+
}
194+
);
165195

166196
const styles = StyleSheet.create({
167197
cardsContainer: {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { default as DeckSwiper } from "./DeckSwiper";
1+
export { default as DeckSwiper, DeckSwiperRef } from "./DeckSwiper";
22
export { default as DeckSwiperCard } from "./DeckSwiperCard";

packages/core/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export {
3434
} from "./components/RadioButton/index";
3535
export { default as Shadow } from "./components/Shadow";
3636
export { DeckSwiper, DeckSwiperCard } from "./components/DeckSwiper";
37+
export type { DeckSwiperRef } from "./components/DeckSwiper";
3738
export { TabView, TabViewItem } from "./components/TabView";
3839
export { default as Markdown } from "./components/Markdown";
3940
export { BottomSheet } from "./components/BottomSheet";

0 commit comments

Comments
 (0)