ZAYX logo
XGitHubDiscordReddit

Piggy Bank

A gravity-driven savings drop animated coins fall from your quick-amount pad into a reactive piggy bank.

Open in Github
Open in ChatGPT
Open in Claude
Open in Cursor

Physics-y coin fall

Staggered delay, rotateY flip loop, sideways drift, spring-settle, scale+fade absorb.

Self-measuring coin slot

Piggy bank measures its own slot position via UI-thread measure(), no hardcoded coordinates.

Imperative piggy reactions

jiggle() per coin, proudPuff() (spring overshoot + success haptic) on full deposit.

Race-safe save sync

Success only fires once both the coin animation and your onSave promise resolve; a 3s grace timer catches slow API calls without ever double-firing.

Installation

Copy the command directly into your project.

npx degit ManasCodeXart/ZAYX/components/piggy-bank components/piggy-bank

Install the needed dependencies

npx expo install react-native-reanimated react-native-worklets expo-haptics react-native-safe-area-context

Use in your app

Built to match the preview exactly — for the complete implementation, check the repo on GitHub.

import { useCallback, useState } from 'react' import GravitySavings from '../../components/piggy-bank/components/GravitySavings' const DEMO_AVATAR = require('../../components/piggy-bank/assets/images/Avatar.png') const DEMO_INITIAL_SAVINGS = 3600 const SAVE_SIMULATION_DELAY = 1200 export default function GravitySavingsDemo() { const [currentSavings, setCurrentSavings] = useState(DEMO_INITIAL_SAVINGS) const handleSave = useCallback(async (amount: number) => { // Replace with a real deposit/payment API call. await new Promise<void>((resolve) => setTimeout(resolve, SAVE_SIMULATION_DELAY)) setCurrentSavings((prev) => prev + amount) }, []) const handleError = useCallback((error: unknown) => { console.warn('Save failed:', error) }, []) return ( <GravitySavings currentSavings={currentSavings} userName="ManasCodeXart" userAvatar={DEMO_AVATAR} onSave={handleSave} onError={handleError} /> ) }

API

<GravitySavings>

PropTypeDefaultDescription
currentSavingsnumberBalance shown at the top. Controlled — update it yourself inside onSave.
userNamestringShown on the success sheet.
userAvatarImageSourcePropTypeOptional. Shown next to the balance and on the success sheet.
currencySymbolstring'$'Prefix used everywhere an amount is shown.
quickAmountsreadonly QuickAmountPill[]$100 / $500 / $1000Pills above the keypad.
hapticsEnabledbooleantrueEnables keypad, jiggle, and success haptics.
onSave(amount: number) => void | Promise<void>Required. Called on Save tap. Both the coin animation and this promise must resolve before success shows.
onDone() => voidCalled when the user dismisses the success sheet's Done button.
onReturnHome() => voidOptional. Adds a secondary Return to Home link to the success sheet.
onError(error: unknown) => voidCalled if onSave rejects or times out after 3 seconds.

currentSavings is a controlled prop — GravitySavings never updates your balance internally. Add the saved amount to your own state inside onSave.

Types

interface QuickAmountPill { readonly label: string; readonly value: number; } type CoinDropState = 'idle' | 'dropping' | 'success' | 'error';

Project Structure

piggy-bank/
├── components/
│ ├── GravitySavings.tsx # Root component — orchestrates drop + save flow
│ ├── PiggyBank.tsx # Coin slot measurement, jiggle + proud-puff reactions
│ ├── Coin.tsx # Physics-driven coin fall + absorb animation
│ ├── AnimatedCounter.tsx # Shared-value digit counter
│ ├── Keypad.tsx # Custom numeric keypad
│ └── SuccessSheet.tsx # Spring-in confirmation sheet
├── constants/
│ ├── types.ts # QuickAmountPill, CoinDropState types
│ └── scaling.ts # verticalScale helpers
├── utils/
│ └── format.ts # Amount formatting helpers
└── assets/