Skip to content

Commit ada6f62

Browse files
committed
test: add component tests, example screens, and playground components
- Add comprehensive Carousel.test.tsx with rendering, props, accessibility, preset, pagination, autoplay, loop, and callback tests - Add PanGestureManager.test.ts with gesture config, snap, velocity, and spring config tests - Add BasicExample, AdvancedExample, CustomExample screens for expo-example - Add bare-example entry point and app.json - Add CodePreview and PropsEditor playground components - Add docs static assets directory
1 parent 52a28a8 commit ada6f62

10 files changed

Lines changed: 2610 additions & 0 deletions

File tree

examples/bare-example/app.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "BareExample",
3+
"displayName": "Bare Example"
4+
}

examples/bare-example/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @format
3+
* @description Entry point for the bare workflow React Native example app.
4+
*/
5+
6+
import { AppRegistry } from 'react-native';
7+
import App from './App';
8+
import { name as appName } from './app.json';
9+
10+
AppRegistry.registerComponent(appName, () => App);
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/**
2+
* @file AdvancedExample screen
3+
* @description Demonstrates advanced carousel presets: stack, tinder, coverflow, cube, flip.
4+
* Features an interactive preset selector to switch between animations.
5+
* @author toankhontech
6+
*/
7+
8+
import React, { useState } from 'react';
9+
import {
10+
View,
11+
Text,
12+
ScrollView,
13+
StyleSheet,
14+
Dimensions,
15+
TouchableOpacity,
16+
SafeAreaView,
17+
} from 'react-native';
18+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
19+
import { Carousel, useCarousel } from 'react-native-ultra-carousel';
20+
import type { PresetName } from 'react-native-ultra-carousel';
21+
22+
const { width: SCREEN_WIDTH } = Dimensions.get('window');
23+
24+
const COLORS = ['#6C5CE7', '#00CEC9', '#FD79A8', '#FDCB6E', '#55EFC4', '#74B9FF', '#E17055'];
25+
26+
const DATA = COLORS.map((color, i) => ({
27+
id: String(i + 1),
28+
title: `Card ${i + 1}`,
29+
subtitle: `Swipe to explore`,
30+
color,
31+
}));
32+
33+
interface AdvancedPresetInfo {
34+
name: PresetName;
35+
label: string;
36+
description: string;
37+
}
38+
39+
const ADVANCED_PRESETS: AdvancedPresetInfo[] = [
40+
{
41+
name: 'stack',
42+
label: 'Stack',
43+
description: 'Cards stack on top of each other with a layered depth effect.',
44+
},
45+
{
46+
name: 'tinder',
47+
label: 'Tinder',
48+
description: 'Swipe cards left or right with a rotating throw animation.',
49+
},
50+
{
51+
name: 'coverflow',
52+
label: 'Coverflow',
53+
description: 'Items rotate and recede in 3D, similar to the classic album art view.',
54+
},
55+
{
56+
name: 'cube',
57+
label: 'Cube',
58+
description: 'Items are placed on the faces of a rotating 3D cube.',
59+
},
60+
{
61+
name: 'flip',
62+
label: 'Flip',
63+
description: 'Items flip around the Y-axis to reveal the next card.',
64+
},
65+
];
66+
67+
const CAROUSEL_WIDTH = SCREEN_WIDTH - 40;
68+
const CAROUSEL_HEIGHT = 260;
69+
70+
export default function AdvancedExample() {
71+
const [selectedIndex, setSelectedIndex] = useState(0);
72+
const carousel = useCarousel();
73+
74+
const activePreset = ADVANCED_PRESETS[selectedIndex];
75+
76+
return (
77+
<GestureHandlerRootView style={styles.flex}>
78+
<SafeAreaView style={styles.flex}>
79+
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
80+
<Text style={styles.header}>Advanced Presets</Text>
81+
<Text style={styles.subheader}>
82+
Tap a preset below to see it in action
83+
</Text>
84+
85+
{/* Carousel display */}
86+
<View style={styles.carouselContainer}>
87+
<Carousel
88+
ref={carousel.ref}
89+
data={DATA}
90+
renderItem={({ item }) => (
91+
<View style={[styles.card, { backgroundColor: item.color }]}>
92+
<Text style={styles.cardTitle}>{item.title}</Text>
93+
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
94+
<View style={styles.presetBadge}>
95+
<Text style={styles.presetBadgeText}>{activePreset.name}</Text>
96+
</View>
97+
</View>
98+
)}
99+
preset={activePreset.name}
100+
width={CAROUSEL_WIDTH}
101+
height={CAROUSEL_HEIGHT}
102+
pagination
103+
gap={12}
104+
/>
105+
</View>
106+
107+
<Text style={styles.counter}>
108+
{carousel.activeIndex + 1} / {DATA.length}
109+
</Text>
110+
111+
{/* Preset description */}
112+
<View style={styles.descriptionBox}>
113+
<Text style={styles.descriptionTitle}>{activePreset.label}</Text>
114+
<Text style={styles.descriptionText}>{activePreset.description}</Text>
115+
</View>
116+
117+
{/* Preset selector */}
118+
<Text style={styles.selectorLabel}>Choose a preset</Text>
119+
<View style={styles.selectorContainer}>
120+
{ADVANCED_PRESETS.map((preset, index) => {
121+
const isActive = index === selectedIndex;
122+
return (
123+
<TouchableOpacity
124+
key={preset.name}
125+
style={[styles.selectorButton, isActive && styles.selectorButtonActive]}
126+
onPress={() => setSelectedIndex(index)}
127+
activeOpacity={0.7}
128+
>
129+
<Text
130+
style={[
131+
styles.selectorButtonText,
132+
isActive && styles.selectorButtonTextActive,
133+
]}
134+
>
135+
{preset.label}
136+
</Text>
137+
</TouchableOpacity>
138+
);
139+
})}
140+
</View>
141+
142+
<View style={styles.spacer} />
143+
</ScrollView>
144+
</SafeAreaView>
145+
</GestureHandlerRootView>
146+
);
147+
}
148+
149+
const styles = StyleSheet.create({
150+
flex: {
151+
flex: 1,
152+
},
153+
container: {
154+
flex: 1,
155+
backgroundColor: '#f8f9fa',
156+
},
157+
header: {
158+
fontSize: 26,
159+
fontWeight: '800',
160+
textAlign: 'center',
161+
marginTop: 20,
162+
color: '#1a1a2e',
163+
},
164+
subheader: {
165+
fontSize: 13,
166+
color: '#888',
167+
textAlign: 'center',
168+
marginBottom: 20,
169+
},
170+
carouselContainer: {
171+
alignItems: 'center',
172+
},
173+
card: {
174+
flex: 1,
175+
borderRadius: 16,
176+
justifyContent: 'center',
177+
alignItems: 'center',
178+
paddingVertical: 20,
179+
},
180+
cardTitle: {
181+
fontSize: 28,
182+
fontWeight: '800',
183+
color: '#fff',
184+
},
185+
cardSubtitle: {
186+
fontSize: 13,
187+
color: 'rgba(255, 255, 255, 0.75)',
188+
marginTop: 4,
189+
},
190+
presetBadge: {
191+
marginTop: 16,
192+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
193+
paddingHorizontal: 14,
194+
paddingVertical: 5,
195+
borderRadius: 12,
196+
},
197+
presetBadgeText: {
198+
fontSize: 11,
199+
fontWeight: '600',
200+
color: '#fff',
201+
textTransform: 'uppercase',
202+
letterSpacing: 1,
203+
},
204+
counter: {
205+
textAlign: 'center',
206+
color: '#888',
207+
fontSize: 13,
208+
marginTop: 8,
209+
marginBottom: 16,
210+
},
211+
descriptionBox: {
212+
marginHorizontal: 20,
213+
backgroundColor: '#fff',
214+
borderRadius: 12,
215+
padding: 16,
216+
marginBottom: 20,
217+
shadowColor: '#000',
218+
shadowOffset: { width: 0, height: 1 },
219+
shadowOpacity: 0.06,
220+
shadowRadius: 4,
221+
elevation: 2,
222+
},
223+
descriptionTitle: {
224+
fontSize: 16,
225+
fontWeight: '700',
226+
color: '#333',
227+
marginBottom: 4,
228+
},
229+
descriptionText: {
230+
fontSize: 13,
231+
color: '#777',
232+
lineHeight: 19,
233+
},
234+
selectorLabel: {
235+
fontSize: 14,
236+
fontWeight: '600',
237+
color: '#555',
238+
marginLeft: 20,
239+
marginBottom: 10,
240+
},
241+
selectorContainer: {
242+
flexDirection: 'row',
243+
flexWrap: 'wrap',
244+
paddingHorizontal: 16,
245+
gap: 10,
246+
},
247+
selectorButton: {
248+
paddingHorizontal: 18,
249+
paddingVertical: 10,
250+
borderRadius: 20,
251+
backgroundColor: '#e9ecef',
252+
minWidth: 80,
253+
alignItems: 'center',
254+
},
255+
selectorButtonActive: {
256+
backgroundColor: '#6C5CE7',
257+
},
258+
selectorButtonText: {
259+
fontSize: 13,
260+
fontWeight: '600',
261+
color: '#555',
262+
},
263+
selectorButtonTextActive: {
264+
color: '#fff',
265+
},
266+
spacer: {
267+
height: 40,
268+
},
269+
});

0 commit comments

Comments
 (0)