# react-native-wallet-card-stack

A customizable React Native component for displaying animated stacks of card groups. Each group scrolls with smooth animations, and within each group, up to 3 cards are stacked with their own animation effects. Ideal for creating visually engaging card-based UIs like wallets, galleries, or dashboards.

![Demo Image](assets/demo.png)

### Check out the `CardStack` component in action:

[Demo Video](https://www.youtube.com/shorts/y9ets6PTXmY)

## Note :

The whole card stack moving animation currently only works smoothly on iOS.

# Features

- **Animated Scrolling**: Card groups animate as you scroll, with the entire stack shifting based on a configurable top offset.

- **Customizable**: Adjust card height, padding, spacing, and more via props.

- **Group Limit**: Automatically limits each group to a maximum of 3 cards.

- **TypeScript Support**: Fully typed for better developer experience.

## Installation

Install the package via npm:

npm install react-native-card-stack

Or with yarn:

yarn add react-native-card-stack

### Dependencies

Ensure you have the following peer dependencies installed:

- react

- react-native

## Usage

Here’s a basic example of how to use CardStack in your React Native app:

    import React from "react";
    import { SafeAreaView, StyleSheet, Text, View } from "react-native";
    import CardStack from "react-native-wallet-card-stack";


    const Base = () => {
      const [selectedGroup, setSelectedGroup] = React.useState<string | null>(null);

      // Handler for card press events
      const handleCardPress = (groupIndex: number) => {
        setSelectedGroup(testData[groupIndex][0].city);
        console.log(
          `Pressed group at index: ${groupIndex} - ${testData[groupIndex][0].city}`
        );
      };

      return (
        <SafeAreaView style={styles.container}>
          <Text style={styles.header}>Card Stack Demo</Text>
          {/* Display selected group info */}
          <View style={styles.infoContainer}>
            {selectedGroup !== null ? (
              <Text style={styles.infoText}>Selected Group: {selectedGroup}</Text>
            ) : (
              <Text style={styles.placeholderText}>Tap a card group to select</Text>
            )}
          </View>
          {/* Spacer before the card stack */}
          <View style={styles.spacer} />
          {/* Card Stack Component */}
          <CardStack
            data={testData}
            renderCard={(item, groupIndex, cardIndex) => (
              <View style={{ padding: 10 }}>
                <Text>{item.city}</Text>
              </View>
            )}
            cardHeight={400}
            cardPadding={50}
            cardSpace={10}
            topOffset={200}
            onCardPress={handleCardPress}
          />
        </SafeAreaView>
      );
    };

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#f5f5f5",
      },
      header: {
        fontSize: 24,
        fontWeight: "bold",
        padding: 20,
        textAlign: "center",
      },
      infoContainer: {
        paddingHorizontal: 20,
        paddingVertical: 10,
        alignItems: "center",
      },
      infoText: {
        fontSize: 18,
        color: "#fff",
        backgroundColor: "#4CAF50",
        padding: 10,
        borderRadius: 8,
      },
      placeholderText: {
        fontSize: 16,
        color: "#666",
      },
      spacer: {
        height: 200, // Reduced from 300 for better balance
      },
      cardContent: {
        padding: 15,
        backgroundColor: "#fff",
        borderRadius: 8,
        height: "100%", // Ensure it fills the card height
        justifyContent: "center",
      },
      cardTitle: {
        fontSize: 20,
        fontWeight: "bold",
        marginBottom: 5,
      },
      cardDescription: {
        fontSize: 14,
        color: "#333",
      },
    });

    export default Base;

    export const testData = [
      // New York
      [
        { city: 'New York', sold: 75 },
        { city: 'New York', sold: 60 },
      ],
      // Philadelphia
      [
        { city: 'Philadelphia', sold: 85 },
        { city: 'Philadelphia', sold: 65 },
      ],
      // Los Angeles
      [
        { city: 'Los Angeles', sold: 40 },
        { city: 'Los Angeles', sold: 70 },
      ],
      // Chicago
      [
        { city: 'Chicago', sold: 55 },
        { city: 'Chicago', sold: 45 },
      ],
      // Miami
      [
        { city: 'Miami', sold: 65 },
        { city: 'Miami', sold: 50 },
      ],
    ];

## Props

SmartyPants converts ASCII punctuation characters into "smart" typographic punctuation HTML entities. For example:

## Props

| Prop                 | ASCII Type                                                        | ASCII Default                                         | Description                                                                    |
| -------------------- | ----------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| `data`               | `T[][]`                                                           | Required                                              | Array of card groups, where each group is an array of card data objects.       |
| `renderCard`         | `(item: T, groupIndex: number, cardIndex: number) => JSX.Element` | Required                                              | Function to render each card, receiving the item, group index, and card index. |
| `cardHeight`         | `number`                                                          | `400`                                                 | Height of each card in pixels.                                                 |
| `cardPadding`        | `number`                                                          | `57`                                                  | Padding between card groups in the animation.                                  |
| `cardSpace`          | `number`                                                          | `6`                                                   | Space between cards within a group in the animation.                           |
| `topOffset`          | `number`                                                          | `Dimensions.get('window').height * 0.35`              | Top offset for the stack animation trigger.                                    |
| `keyExtractor`       | `(item: T[], groupIndex: number) => string`                       | `(item, index) => ${index}`                           | Key extractor for card groups.                                                 |
| `cardKeyExtractor`   | `(item: T, cardIndex: number, groupIndex: number) => string`      | `(item, index, groupIndex) => ${groupIndex}-${index}` | Key extractor for cards within a group.                                        |
| `onCardPress`        | `(groupIndex: number) => void`                                    | `undefined`                                           | Callback when a card group is pressed, receives the group index.               |
| `flatListProps`      | `Partial<FlatListProps<T[]>>`                                     | `undefined`                                           | Additional props for the outer FlatList.                                       |
| `innerFlatListProps` | `Partial<FlatListProps<T>>`                                       | `undefined`                                           | Additional props for the inner FlatList (per group).                           |

```

```
