Skip to content

Commit 52a28a8

Browse files
committed
feat: add ecosystem packages, built-in plugins, and documentation
- Built-in plugins: autoPlay, pagination, parallaxImage - Image gallery with zoom and swipe-to-dismiss - Onboarding carousel with progress and skip - Theme system with 4 built-in themes (light, dark, vibrant, minimal) - RTL layout support utilities - Performance monitor dev overlay - CLI preview command for local demos - Playground app with Expo Router (preset gallery, benchmarks, custom builder) - Docusaurus documentation site with 20+ pages - VS Code extension with 8 code snippets - E2E test setup with Detox - Storybook stories for visual testing - CI workflows for bundle size checking and E2E tests - Build, release, benchmark, and docs generation scripts - Bump to v1.0.0
1 parent b46e497 commit 52a28a8

64 files changed

Lines changed: 6787 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
e2e-ios:
11+
runs-on: macos-14
12+
timeout-minutes: 60
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
cache: 'yarn'
19+
- run: yarn install --frozen-lockfile
20+
- run: yarn build
21+
- name: Install Detox CLI
22+
run: npm install -g detox-cli
23+
- name: Build Detox app
24+
working-directory: examples/expo-example
25+
run: detox build --configuration ios.sim.release
26+
- name: Run Detox tests
27+
working-directory: examples/expo-example
28+
run: detox test --configuration ios.sim.release --cleanup

.github/workflows/size-check.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Bundle Size
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
7+
jobs:
8+
size:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-node@v4
13+
with:
14+
node-version: 20
15+
cache: 'yarn'
16+
- run: yarn install --frozen-lockfile
17+
- run: yarn build
18+
- uses: andresz1/size-limit-action@v1
19+
with:
20+
github_token: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @file preview command
3+
* @description Launches a local preview server to demo all presets
4+
*/
5+
6+
import chalk from 'chalk';
7+
import { execSync } from 'child_process';
8+
import path from 'path';
9+
import fs from 'fs-extra';
10+
11+
interface PreviewOptions {
12+
port?: string;
13+
}
14+
15+
export const previewCommand = (options: PreviewOptions): void => {
16+
const port = options.port || '8081';
17+
const playgroundDir = path.resolve(__dirname, '..', '..', '..', 'playground');
18+
19+
if (!fs.existsSync(playgroundDir)) {
20+
console.log(chalk.red('\n Playground app not found.'));
21+
console.log(chalk.dim(' Run from the monorepo root, or install the playground package.\n'));
22+
return;
23+
}
24+
25+
console.log(chalk.bold('\n Starting Ultra Carousel Preview...\n'));
26+
console.log(chalk.dim(` Port: ${port}`));
27+
console.log(chalk.dim(` Directory: ${playgroundDir}\n`));
28+
29+
try {
30+
execSync(`npx expo start --port ${port} --web`, {
31+
cwd: playgroundDir,
32+
stdio: 'inherit',
33+
});
34+
} catch {
35+
console.log(chalk.yellow('\n Preview server stopped.\n'));
36+
}
37+
};

packages/cli/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { Command } from 'commander';
77
import { initCommand } from './commands/init';
88
import { addCommand } from './commands/add';
99
import { listCommand } from './commands/list';
10+
import { previewCommand } from './commands/preview';
1011

1112
const program = new Command();
1213

1314
program
1415
.name('ultra-carousel')
1516
.description('CLI tool for react-native-ultra-carousel')
16-
.version('0.1.0');
17+
.version('0.5.0');
1718

1819
program
1920
.command('init')
@@ -33,4 +34,10 @@ program
3334
.option('-c, --category <name>', 'filter by category (basic, advanced, creative)')
3435
.action(listCommand);
3536

37+
program
38+
.command('preview')
39+
.description('Launch local preview of all presets')
40+
.option('-p, --port <number>', 'port number', '8081')
41+
.action(previewCommand);
42+
3643
program.parse();

packages/core/.storybook/main.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { StorybookConfig } from '@storybook/react-native';
2+
3+
const config: StorybookConfig = {
4+
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
5+
addons: [],
6+
};
7+
8+
export default config;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** @type {Detox.DetoxConfig} */
2+
module.exports = {
3+
testRunner: {
4+
args: {
5+
$0: 'jest',
6+
config: 'packages/core/__tests__/e2e/jest.config.js',
7+
},
8+
jest: {
9+
setupTimeout: 120000,
10+
},
11+
},
12+
apps: {
13+
'ios.release': {
14+
type: 'ios.app',
15+
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/UltraCarouselExample.app',
16+
build: 'xcodebuild -workspace ios/UltraCarouselExample.xcworkspace -scheme UltraCarouselExample -configuration Release -sdk iphonesimulator -derivedDataPath ios/build',
17+
},
18+
'android.release': {
19+
type: 'android.apk',
20+
binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
21+
build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
22+
},
23+
},
24+
devices: {
25+
simulator: {
26+
type: 'ios.simulator',
27+
device: { type: 'iPhone 15' },
28+
},
29+
emulator: {
30+
type: 'android.emulator',
31+
device: { avdName: 'Pixel_5_API_34' },
32+
},
33+
},
34+
configurations: {
35+
'ios.sim.release': {
36+
device: 'simulator',
37+
app: 'ios.release',
38+
},
39+
'android.emu.release': {
40+
device: 'emulator',
41+
app: 'android.release',
42+
},
43+
},
44+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @file Carousel E2E tests
3+
* @description End-to-end tests for core carousel functionality
4+
*/
5+
6+
import { device, element, by, expect } from 'detox';
7+
8+
describe('Carousel', () => {
9+
beforeAll(async () => {
10+
await device.launchApp();
11+
});
12+
13+
beforeEach(async () => {
14+
await device.reloadReactNative();
15+
});
16+
17+
it('should render carousel with items', async () => {
18+
await expect(element(by.id('carousel'))).toBeVisible();
19+
await expect(element(by.text('Slide 1'))).toBeVisible();
20+
});
21+
22+
it('should swipe to next item', async () => {
23+
await element(by.id('carousel')).swipe('left');
24+
await expect(element(by.text('Slide 2'))).toBeVisible();
25+
});
26+
27+
it('should swipe to previous item', async () => {
28+
await element(by.id('carousel')).swipe('left');
29+
await element(by.id('carousel')).swipe('right');
30+
await expect(element(by.text('Slide 1'))).toBeVisible();
31+
});
32+
33+
it('should show pagination dots', async () => {
34+
await expect(element(by.id('pagination'))).toBeVisible();
35+
});
36+
37+
it('should change preset', async () => {
38+
await element(by.text('fade')).tap();
39+
await expect(element(by.text('fade'))).toBeVisible();
40+
});
41+
42+
it('should handle rapid swipes', async () => {
43+
await element(by.id('carousel')).swipe('left');
44+
await element(by.id('carousel')).swipe('left');
45+
await element(by.id('carousel')).swipe('left');
46+
await expect(element(by.text('Slide 4'))).toBeVisible();
47+
});
48+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('jest').Config} */
2+
module.exports = {
3+
rootDir: '../..',
4+
testMatch: ['<rootDir>/__tests__/e2e/**/*.e2e.ts'],
5+
testTimeout: 120000,
6+
maxWorkers: 1,
7+
globalSetup: 'detox/runners/jest/globalSetup',
8+
globalTeardown: 'detox/runners/jest/globalTeardown',
9+
reporters: ['detox/runners/jest/reporter'],
10+
testEnvironment: 'detox/runners/jest/testEnvironment',
11+
verbose: true,
12+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @file Animation preset E2E tests
3+
* @description Tests for individual animation presets
4+
*/
5+
6+
import { device, element, by, expect } from 'detox';
7+
8+
const PRESETS_TO_TEST = ['slide', 'fade', 'coverflow', 'cube', 'tinder', 'stack', 'newspaper', 'wave'];
9+
10+
describe('Animation Presets', () => {
11+
beforeAll(async () => {
12+
await device.launchApp();
13+
});
14+
15+
beforeEach(async () => {
16+
await device.reloadReactNative();
17+
});
18+
19+
PRESETS_TO_TEST.forEach((preset) => {
20+
it(`should render ${preset} preset without crashing`, async () => {
21+
// Select the preset
22+
await element(by.text(preset)).tap();
23+
24+
// Verify carousel is still visible
25+
await expect(element(by.id('carousel'))).toBeVisible();
26+
27+
// Swipe to test animation runs without crash
28+
await element(by.id('carousel')).swipe('left');
29+
await element(by.id('carousel')).swipe('right');
30+
});
31+
});
32+
});

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-ultra-carousel",
3-
"version": "0.5.0",
3+
"version": "1.0.0",
44
"description": "The Ultimate Carousel Ecosystem for React Native. 35+ animation presets, plugin system, CLI tool. Built on Reanimated.",
55
"main": "lib/commonjs/index.js",
66
"module": "lib/module/index.js",

0 commit comments

Comments
 (0)